blob: a24de00736cb33e7e4104f545ef5faf66414ca0d [file] [log] [blame]
tierno7edb6752016-03-21 17:37:52 +01001# -*- coding: utf-8 -*-
2
3##
tierno92021022018-09-12 16:29:23 +02004# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
tierno7edb6752016-03-21 17:37:52 +01005# 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'''
Eduardo Sousae3c0dbc2018-09-03 11:56:07 +010035__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C., Eduardo Sousa"
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000036__date__ = "$22-sep-2017 23:59:59$"
tierno7edb6752016-03-21 17:37:52 +010037
38import vimconn
tierno69b590e2018-03-13 18:52:23 +010039# import 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
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010047from pprint import pformat
48from types import StringTypes
tierno7edb6752016-03-21 17:37:52 +010049
tiernob5cef372017-06-19 15:52:22 +020050from novaclient import client as nClient, exceptions as nvExceptions
51from keystoneauth1.identity import v2, v3
52from keystoneauth1 import session
tierno7edb6752016-03-21 17:37:52 +010053import keystoneclient.exceptions as ksExceptions
tiernof716aea2017-06-21 18:01:40 +020054import keystoneclient.v3.client as ksClient_v3
55import keystoneclient.v2_0.client as ksClient_v2
tiernob5cef372017-06-19 15:52:22 +020056from glanceclient import client as glClient
tierno7edb6752016-03-21 17:37:52 +010057import glanceclient.exc as gl1Exceptions
tiernob5cef372017-06-19 15:52:22 +020058from cinderclient import client as cClient
tierno7edb6752016-03-21 17:37:52 +010059from httplib import HTTPException
tiernob5cef372017-06-19 15:52:22 +020060from neutronclient.neutron import client as neClient
tierno7edb6752016-03-21 17:37:52 +010061from neutronclient.common import exceptions as neExceptions
62from requests.exceptions import ConnectionError
63
tierno40e1bce2017-08-09 09:12:04 +020064
65"""contain the openstack virtual machine status to openmano status"""
tierno7edb6752016-03-21 17:37:52 +010066vmStatus2manoFormat={'ACTIVE':'ACTIVE',
67 'PAUSED':'PAUSED',
68 'SUSPENDED': 'SUSPENDED',
69 'SHUTOFF':'INACTIVE',
70 'BUILD':'BUILD',
71 'ERROR':'ERROR','DELETED':'DELETED'
72 }
73netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
74 }
75
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000076supportedClassificationTypes = ['legacy_flow_classifier']
77
montesmoreno0c8def02016-12-22 12:16:23 +000078#global var to have a timeout creating and deleting volumes
tierno00e3df72017-11-29 17:20:13 +010079volume_timeout = 600
80server_timeout = 600
montesmoreno0c8def02016-12-22 12:16:23 +000081
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010082
83class SafeDumper(yaml.SafeDumper):
84 def represent_data(self, data):
85 # Openstack APIs use custom subclasses of dict and YAML safe dumper
86 # is designed to not handle that (reference issue 142 of pyyaml)
87 if isinstance(data, dict) and data.__class__ != dict:
88 # A simple solution is to convert those items back to dicts
89 data = dict(data.items())
90
91 return super(SafeDumper, self).represent_data(data)
92
93
tierno7edb6752016-03-21 17:37:52 +010094class vimconnector(vimconn.vimconnector):
tiernob3d36742017-03-03 23:51:05 +010095 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
96 log_level=None, config={}, persistent_info={}):
ahmadsa96af9f42017-01-31 16:17:14 +050097 '''using common constructor parameters. In this case
tierno7edb6752016-03-21 17:37:52 +010098 'url' is the keystone authorization url,
99 'url_admin' is not use
100 '''
tiernof716aea2017-06-21 18:01:40 +0200101 api_version = config.get('APIversion')
102 if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
tiernob5cef372017-06-19 15:52:22 +0200103 raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
tiernof716aea2017-06-21 18:01:40 +0200104 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
kate721d79b2017-06-24 04:21:38 -0700105 vim_type = config.get('vim_type')
106 if vim_type and vim_type not in ('vio', 'VIO'):
107 raise vimconn.vimconnException("Invalid value '{}' for config:vim_type."
108 "Allowed values are 'vio' or 'VIO'".format(vim_type))
109
110 if config.get('dataplane_net_vlan_range') is not None:
111 #validate vlan ranges provided by user
112 self._validate_vlan_ranges(config.get('dataplane_net_vlan_range'))
113
tiernob5cef372017-06-19 15:52:22 +0200114 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
115 config)
tiernob3d36742017-03-03 23:51:05 +0100116
tierno4d1ce222018-04-06 10:41:06 +0200117 if self.config.get("insecure") and self.config.get("ca_cert"):
118 raise vimconn.vimconnException("options insecure and ca_cert are mutually exclusive")
119 self.verify = True
120 if self.config.get("insecure"):
121 self.verify = False
122 if self.config.get("ca_cert"):
123 self.verify = self.config.get("ca_cert")
tierno4d1ce222018-04-06 10:41:06 +0200124
tierno7edb6752016-03-21 17:37:52 +0100125 if not url:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000126 raise TypeError('url param can not be NoneType')
tiernob5cef372017-06-19 15:52:22 +0200127 self.persistent_info = persistent_info
mirabal29356312017-07-27 12:21:22 +0200128 self.availability_zone = persistent_info.get('availability_zone', None)
tiernob5cef372017-06-19 15:52:22 +0200129 self.session = persistent_info.get('session', {'reload_client': True})
130 self.nova = self.session.get('nova')
131 self.neutron = self.session.get('neutron')
132 self.cinder = self.session.get('cinder')
133 self.glance = self.session.get('glance')
tierno1beea862018-07-11 15:47:37 +0200134 # self.glancev1 = self.session.get('glancev1')
tiernof716aea2017-06-21 18:01:40 +0200135 self.keystone = self.session.get('keystone')
136 self.api_version3 = self.session.get('api_version3')
kate721d79b2017-06-24 04:21:38 -0700137 self.vim_type = self.config.get("vim_type")
138 if self.vim_type:
139 self.vim_type = self.vim_type.upper()
140 if self.config.get("use_internal_endpoint"):
141 self.endpoint_type = "internalURL"
142 else:
143 self.endpoint_type = None
montesmoreno0c8def02016-12-22 12:16:23 +0000144
tierno73ad9e42016-09-12 18:11:11 +0200145 self.logger = logging.getLogger('openmano.vim.openstack')
kate721d79b2017-06-24 04:21:38 -0700146
147 ####### VIO Specific Changes #########
148 if self.vim_type == "VIO":
149 self.logger = logging.getLogger('openmano.vim.vio')
150
tiernofe789902016-09-29 14:20:44 +0000151 if log_level:
kate54616752017-09-05 23:26:28 -0700152 self.logger.setLevel( getattr(logging, log_level))
tiernof716aea2017-06-21 18:01:40 +0200153
154 def __getitem__(self, index):
155 """Get individuals parameters.
156 Throw KeyError"""
157 if index == 'project_domain_id':
158 return self.config.get("project_domain_id")
159 elif index == 'user_domain_id':
160 return self.config.get("user_domain_id")
161 else:
tierno76a3c312017-06-29 16:42:15 +0200162 return vimconn.vimconnector.__getitem__(self, index)
tiernof716aea2017-06-21 18:01:40 +0200163
164 def __setitem__(self, index, value):
165 """Set individuals parameters and it is marked as dirty so to force connection reload.
166 Throw KeyError"""
167 if index == 'project_domain_id':
168 self.config["project_domain_id"] = value
169 elif index == 'user_domain_id':
170 self.config["user_domain_id"] = value
171 else:
172 vimconn.vimconnector.__setitem__(self, index, value)
tiernob5cef372017-06-19 15:52:22 +0200173 self.session['reload_client'] = True
tiernof716aea2017-06-21 18:01:40 +0200174
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100175 def serialize(self, value):
176 """Serialization of python basic types.
177
178 In the case value is not serializable a message will be logged and a
179 simple representation of the data that cannot be converted back to
180 python is returned.
181 """
182 if isinstance(value, StringTypes):
183 return value
184
185 try:
186 return yaml.dump(value, Dumper=SafeDumper,
187 default_flow_style=True, width=256)
188 except yaml.representer.RepresenterError:
189 self.logger.debug(
190 'The following entity cannot be serialized in YAML:'
191 '\n\n%s\n\n', pformat(value), exc_info=True)
192 return str(value)
193
tierno7edb6752016-03-21 17:37:52 +0100194 def _reload_connection(self):
195 '''Called before any operation, it check if credentials has changed
196 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
197 '''
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100198 #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 +0200199 if self.session['reload_client']:
tiernof716aea2017-06-21 18:01:40 +0200200 if self.config.get('APIversion'):
201 self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3'
202 else: # get from ending auth_url that end with v3 or with v2.0
tierno3cb8dc32017-10-24 18:13:19 +0200203 self.api_version3 = self.url.endswith("/v3") or self.url.endswith("/v3/")
tiernof716aea2017-06-21 18:01:40 +0200204 self.session['api_version3'] = self.api_version3
205 if self.api_version3:
tierno3cb8dc32017-10-24 18:13:19 +0200206 if self.config.get('project_domain_id') or self.config.get('project_domain_name'):
207 project_domain_id_default = None
208 else:
209 project_domain_id_default = 'default'
210 if self.config.get('user_domain_id') or self.config.get('user_domain_name'):
211 user_domain_id_default = None
212 else:
213 user_domain_id_default = 'default'
tiernof716aea2017-06-21 18:01:40 +0200214 auth = v3.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200215 username=self.user,
216 password=self.passwd,
217 project_name=self.tenant_name,
218 project_id=self.tenant_id,
tierno3cb8dc32017-10-24 18:13:19 +0200219 project_domain_id=self.config.get('project_domain_id', project_domain_id_default),
220 user_domain_id=self.config.get('user_domain_id', user_domain_id_default),
221 project_domain_name=self.config.get('project_domain_name'),
222 user_domain_name=self.config.get('user_domain_name'))
ahmadsa95baa272016-11-30 09:14:11 +0500223 else:
tiernof716aea2017-06-21 18:01:40 +0200224 auth = v2.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200225 username=self.user,
226 password=self.passwd,
227 tenant_name=self.tenant_name,
228 tenant_id=self.tenant_id)
tierno4d1ce222018-04-06 10:41:06 +0200229 sess = session.Session(auth=auth, verify=self.verify)
tiernof716aea2017-06-21 18:01:40 +0200230 if self.api_version3:
fatollahy73015ce2019-02-01 20:50:46 +0000231 if self.config.get('region_name'):
232 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type, region_name=self.config['region_name'])
233 else:
234 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200235 else:
kate721d79b2017-06-24 04:21:38 -0700236 self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200237 self.session['keystone'] = self.keystone
montesmoreno9317d302017-08-16 12:48:23 +0200238 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
239 # This implementation approach is due to the warning message in
240 # https://developer.openstack.org/api-guide/compute/microversions.html
241 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
242 # always require an specific microversion.
243 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
244 version = self.config.get("microversion")
245 if not version:
246 version = "2.1"
fatollahy73015ce2019-02-01 20:50:46 +0000247 # Added region to support Distributed cloud as implemented in WindRiver Titanium Cloud and StarlingX
248 if self.config.get('region_name'):
249 self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type, region_name=self.config['region_name'])
250 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type, region_name=self.config['region_name'])
251 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type, region_name=self.config['region_name'])
252 else:
253 self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type)
254 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type)
255 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type)
kate721d79b2017-06-24 04:21:38 -0700256 if self.endpoint_type == "internalURL":
257 glance_service_id = self.keystone.services.list(name="glance")[0].id
258 glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
259 else:
260 glance_endpoint = None
261 self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
262 #using version 1 of glance client in new_image()
tierno1beea862018-07-11 15:47:37 +0200263 # self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
264 # endpoint=glance_endpoint)
tiernob5cef372017-06-19 15:52:22 +0200265 self.session['reload_client'] = False
266 self.persistent_info['session'] = self.session
mirabal29356312017-07-27 12:21:22 +0200267 # add availablity zone info inside self.persistent_info
268 self._set_availablity_zones()
269 self.persistent_info['availability_zone'] = self.availability_zone
ahmadsa95baa272016-11-30 09:14:11 +0500270
tierno7edb6752016-03-21 17:37:52 +0100271 def __net_os2mano(self, net_list_dict):
272 '''Transform the net openstack format to mano format
273 net_list_dict can be a list of dict or a single dict'''
274 if type(net_list_dict) is dict:
275 net_list_=(net_list_dict,)
276 elif type(net_list_dict) is list:
277 net_list_=net_list_dict
278 else:
279 raise TypeError("param net_list_dict must be a list or a dictionary")
280 for net in net_list_:
281 if net.get('provider:network_type') == "vlan":
282 net['type']='data'
283 else:
284 net['type']='bridge'
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200285
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000286 def __classification_os2mano(self, class_list_dict):
287 """Transform the openstack format (Flow Classifier) to mano format
288 (Classification) class_list_dict can be a list of dict or a single dict
289 """
290 if isinstance(class_list_dict, dict):
291 class_list_ = [class_list_dict]
292 elif isinstance(class_list_dict, list):
293 class_list_ = class_list_dict
294 else:
295 raise TypeError(
296 "param class_list_dict must be a list or a dictionary")
297 for classification in class_list_:
298 id = classification.pop('id')
299 name = classification.pop('name')
300 description = classification.pop('description')
301 project_id = classification.pop('project_id')
302 tenant_id = classification.pop('tenant_id')
303 original_classification = copy.deepcopy(classification)
304 classification.clear()
305 classification['ctype'] = 'legacy_flow_classifier'
306 classification['definition'] = original_classification
307 classification['id'] = id
308 classification['name'] = name
309 classification['description'] = description
310 classification['project_id'] = project_id
311 classification['tenant_id'] = tenant_id
312
313 def __sfi_os2mano(self, sfi_list_dict):
314 """Transform the openstack format (Port Pair) to mano format (SFI)
315 sfi_list_dict can be a list of dict or a single dict
316 """
317 if isinstance(sfi_list_dict, dict):
318 sfi_list_ = [sfi_list_dict]
319 elif isinstance(sfi_list_dict, list):
320 sfi_list_ = sfi_list_dict
321 else:
322 raise TypeError(
323 "param sfi_list_dict must be a list or a dictionary")
324 for sfi in sfi_list_:
325 sfi['ingress_ports'] = []
326 sfi['egress_ports'] = []
327 if sfi.get('ingress'):
328 sfi['ingress_ports'].append(sfi['ingress'])
329 if sfi.get('egress'):
330 sfi['egress_ports'].append(sfi['egress'])
331 del sfi['ingress']
332 del sfi['egress']
333 params = sfi.get('service_function_parameters')
334 sfc_encap = False
335 if params:
336 correlation = params.get('correlation')
337 if correlation:
338 sfc_encap = True
339 sfi['sfc_encap'] = sfc_encap
340 del sfi['service_function_parameters']
341
342 def __sf_os2mano(self, sf_list_dict):
343 """Transform the openstack format (Port Pair Group) to mano format (SF)
344 sf_list_dict can be a list of dict or a single dict
345 """
346 if isinstance(sf_list_dict, dict):
347 sf_list_ = [sf_list_dict]
348 elif isinstance(sf_list_dict, list):
349 sf_list_ = sf_list_dict
350 else:
351 raise TypeError(
352 "param sf_list_dict must be a list or a dictionary")
353 for sf in sf_list_:
354 del sf['port_pair_group_parameters']
355 sf['sfis'] = sf['port_pairs']
356 del sf['port_pairs']
357
358 def __sfp_os2mano(self, sfp_list_dict):
359 """Transform the openstack format (Port Chain) to mano format (SFP)
360 sfp_list_dict can be a list of dict or a single dict
361 """
362 if isinstance(sfp_list_dict, dict):
363 sfp_list_ = [sfp_list_dict]
364 elif isinstance(sfp_list_dict, list):
365 sfp_list_ = sfp_list_dict
366 else:
367 raise TypeError(
368 "param sfp_list_dict must be a list or a dictionary")
369 for sfp in sfp_list_:
370 params = sfp.pop('chain_parameters')
371 sfc_encap = False
372 if params:
373 correlation = params.get('correlation')
374 if correlation:
375 sfc_encap = True
376 sfp['sfc_encap'] = sfc_encap
377 sfp['spi'] = sfp.pop('chain_id')
378 sfp['classifications'] = sfp.pop('flow_classifiers')
379 sfp['service_functions'] = sfp.pop('port_pair_groups')
380
381 # placeholder for now; read TODO note below
382 def _validate_classification(self, type, definition):
383 # only legacy_flow_classifier Type is supported at this point
384 return True
385 # TODO(igordcard): this method should be an abstract method of an
386 # abstract Classification class to be implemented by the specific
387 # Types. Also, abstract vimconnector should call the validation
388 # method before the implemented VIM connectors are called.
389
tiernoae4a8d12016-07-08 12:30:39 +0200390 def _format_exception(self, exception):
391 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
shashankjain3c83a212018-10-04 13:05:46 +0530392 if isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound, ksExceptions.NotFound, gl1Exceptions.HTTPNotFound)):
tiernoae4a8d12016-07-08 12:30:39 +0200393 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
shashankjain3c83a212018-10-04 13:05:46 +0530394 elif isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
395 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed)):
396 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
397 elif isinstance(exception, (KeyError, nvExceptions.BadRequest, ksExceptions.BadRequest)):
anwarsc76a3ee2018-10-04 14:05:32 +0530398 raise vimconn.vimconnException(type(exception).__name__ + ": " + str(exception))
399 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
400 neExceptions.NeutronException)):
401 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
tiernoae4a8d12016-07-08 12:30:39 +0200402 elif isinstance(exception, nvExceptions.Conflict):
403 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200404 elif isinstance(exception, vimconn.vimconnException):
tierno41a69812018-02-16 14:34:33 +0100405 raise exception
tiernof716aea2017-06-21 18:01:40 +0200406 else: # ()
tiernob84cbdc2017-07-07 14:30:30 +0200407 self.logger.error("General Exception " + str(exception), exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +0200408 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
409
410 def get_tenant_list(self, filter_dict={}):
411 '''Obtain tenants of VIM
412 filter_dict can contain the following keys:
413 name: filter by tenant name
414 id: filter by tenant uuid/id
415 <other VIM specific>
416 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
417 '''
ahmadsa95baa272016-11-30 09:14:11 +0500418 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200419 try:
420 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200421 if self.api_version3:
422 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500423 else:
tiernof716aea2017-06-21 18:01:40 +0200424 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500425 project_list=[]
426 for project in project_class_list:
tiernof716aea2017-06-21 18:01:40 +0200427 if filter_dict.get('id') and filter_dict["id"] != project.id:
428 continue
ahmadsa95baa272016-11-30 09:14:11 +0500429 project_list.append(project.to_dict())
430 return project_list
tiernof716aea2017-06-21 18:01:40 +0200431 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200432 self._format_exception(e)
433
434 def new_tenant(self, tenant_name, tenant_description):
435 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
436 self.logger.debug("Adding a new tenant name: %s", tenant_name)
437 try:
438 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200439 if self.api_version3:
440 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
441 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500442 else:
tiernof716aea2017-06-21 18:01:40 +0200443 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500444 return project.id
shashankjain3c83a212018-10-04 13:05:46 +0530445 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200446 self._format_exception(e)
447
448 def delete_tenant(self, tenant_id):
449 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
450 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
451 try:
452 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200453 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500454 self.keystone.projects.delete(tenant_id)
455 else:
456 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200457 return tenant_id
shashankjain3c83a212018-10-04 13:05:46 +0530458 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200459 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500460
garciadeblas9f8456e2016-09-05 05:02:59 +0200461 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200462 '''Adds a tenant network to VIM. Returns the network identifier'''
463 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000464 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100465 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000466 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100467 self._reload_connection()
468 network_dict = {'name': net_name, 'admin_state_up': True}
469 if net_type=="data" or net_type=="ptp":
470 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200471 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100472 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
473 network_dict["provider:network_type"] = "vlan"
474 if vlan!=None:
475 network_dict["provider:network_type"] = vlan
kate721d79b2017-06-24 04:21:38 -0700476
477 ####### VIO Specific Changes #########
478 if self.vim_type == "VIO":
479 if vlan is not None:
480 network_dict["provider:segmentation_id"] = vlan
481 else:
482 if self.config.get('dataplane_net_vlan_range') is None:
483 raise vimconn.vimconnConflictException("You must provide "\
484 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
485 "at config value before creating sriov network with vlan tag")
486
487 network_dict["provider:segmentation_id"] = self._genrate_vlanID()
488
tiernoae4a8d12016-07-08 12:30:39 +0200489 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100490 new_net=self.neutron.create_network({'network':network_dict})
491 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200492 #create subnetwork, even if there is no profile
493 if not ip_profile:
494 ip_profile = {}
tierno41a69812018-02-16 14:34:33 +0100495 if not ip_profile.get('subnet_address'):
garciadeblas2299e3b2017-01-26 14:35:55 +0000496 #Fake subnet is required
497 subnet_rand = random.randint(0, 255)
498 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000499 if 'ip_version' not in ip_profile:
garciadeblas9f8456e2016-09-05 05:02:59 +0200500 ip_profile['ip_version'] = "IPv4"
tiernoa1fb4462017-06-30 12:25:50 +0200501 subnet = {"name":net_name+"-subnet",
tierno7edb6752016-03-21 17:37:52 +0100502 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200503 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
504 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100505 }
tiernoa1fb4462017-06-30 12:25:50 +0200506 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
tierno41a69812018-02-16 14:34:33 +0100507 if ip_profile.get('gateway_address'):
tierno55d234c2018-07-04 18:29:21 +0200508 subnet['gateway_ip'] = ip_profile['gateway_address']
509 else:
510 subnet['gateway_ip'] = None
garciadeblasedca7b32016-09-29 14:01:52 +0000511 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200512 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200513 if 'dhcp_enabled' in ip_profile:
tierno41a69812018-02-16 14:34:33 +0100514 subnet['enable_dhcp'] = False if \
515 ip_profile['dhcp_enabled']=="false" or ip_profile['dhcp_enabled']==False else True
516 if ip_profile.get('dhcp_start_address'):
tiernoa1fb4462017-06-30 12:25:50 +0200517 subnet['allocation_pools'] = []
garciadeblas9f8456e2016-09-05 05:02:59 +0200518 subnet['allocation_pools'].append(dict())
519 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
tierno41a69812018-02-16 14:34:33 +0100520 if ip_profile.get('dhcp_count'):
garciadeblas9f8456e2016-09-05 05:02:59 +0200521 #parts = ip_profile['dhcp_start_address'].split('.')
522 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
523 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200524 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200525 ip_str = str(netaddr.IPAddress(ip_int))
526 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000527 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100528 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200529 return new_net["network"]["id"]
tierno41a69812018-02-16 14:34:33 +0100530 except Exception as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000531 if new_net:
532 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200533 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100534
535 def get_network_list(self, filter_dict={}):
536 '''Obtain tenant networks of VIM
537 Filter_dict can be:
538 name: network name
539 id: network uuid
540 shared: boolean
541 tenant_id: tenant
542 admin_state_up: boolean
543 status: 'ACTIVE'
544 Returns the network list of dictionaries
545 '''
tiernoae4a8d12016-07-08 12:30:39 +0200546 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100547 try:
548 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +0100549 filter_dict_os = filter_dict.copy()
550 if self.api_version3 and "tenant_id" in filter_dict_os:
551 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id') #T ODO check
552 net_dict = self.neutron.list_networks(**filter_dict_os)
tierno00e3df72017-11-29 17:20:13 +0100553 net_list = net_dict["networks"]
tierno7edb6752016-03-21 17:37:52 +0100554 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200555 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000556 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200557 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100558
tiernoae4a8d12016-07-08 12:30:39 +0200559 def get_network(self, net_id):
560 '''Obtain details of network from VIM
561 Returns the network information from a network id'''
562 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100563 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200564 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100565 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200566 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100567 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200568 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100569 net = net_list[0]
570 subnets=[]
571 for subnet_id in net.get("subnets", () ):
572 try:
573 subnet = self.neutron.show_subnet(subnet_id)
574 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200575 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
576 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100577 subnets.append(subnet)
578 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100579 net["encapsulation"] = net.get('provider:network_type')
Anderson Bravalheri0fb70282018-12-16 19:28:37 +0000580 net["encapsulation_type"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100581 net["segmentation_id"] = net.get('provider:segmentation_id')
Anderson Bravalheri0fb70282018-12-16 19:28:37 +0000582 net["encapsulation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200583 return net
tierno7edb6752016-03-21 17:37:52 +0100584
tiernoae4a8d12016-07-08 12:30:39 +0200585 def delete_network(self, net_id):
586 '''Deletes a tenant network from VIM. Returns the old network identifier'''
587 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100588 try:
589 self._reload_connection()
590 #delete VM ports attached to this networks before the network
591 ports = self.neutron.list_ports(network_id=net_id)
592 for p in ports['ports']:
593 try:
594 self.neutron.delete_port(p["id"])
595 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200596 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100597 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200598 return net_id
599 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000600 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200601 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100602
tiernoae4a8d12016-07-08 12:30:39 +0200603 def refresh_nets_status(self, net_list):
604 '''Get the status of the networks
605 Params: the list of network identifiers
606 Returns a dictionary with:
607 net_id: #VIM id of this network
608 status: #Mandatory. Text with one of:
609 # DELETED (not found at vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100610 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
tiernoae4a8d12016-07-08 12:30:39 +0200611 # OTHER (Vim reported other status not understood)
612 # ERROR (VIM indicates an ERROR status)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100613 # ACTIVE, INACTIVE, DOWN (admin down),
tiernoae4a8d12016-07-08 12:30:39 +0200614 # BUILD (on building process)
615 #
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100616 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
tiernoae4a8d12016-07-08 12:30:39 +0200617 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
618
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000619 '''
tiernoae4a8d12016-07-08 12:30:39 +0200620 net_dict={}
621 for net_id in net_list:
622 net = {}
623 try:
624 net_vim = self.get_network(net_id)
625 if net_vim['status'] in netStatus2manoFormat:
626 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
627 else:
628 net["status"] = "OTHER"
629 net["error_msg"] = "VIM status reported " + net_vim['status']
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000630
tierno8e995ce2016-09-22 08:13:00 +0000631 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200632 net['status'] = 'DOWN'
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100633
634 net['vim_info'] = self.serialize(net_vim)
635
tiernoae4a8d12016-07-08 12:30:39 +0200636 if net_vim.get('fault'): #TODO
637 net['error_msg'] = str(net_vim['fault'])
638 except vimconn.vimconnNotFoundException as e:
639 self.logger.error("Exception getting net status: %s", str(e))
640 net['status'] = "DELETED"
641 net['error_msg'] = str(e)
642 except vimconn.vimconnException as e:
643 self.logger.error("Exception getting net status: %s", str(e))
644 net['status'] = "VIM_ERROR"
645 net['error_msg'] = str(e)
646 net_dict[net_id] = net
647 return net_dict
648
649 def get_flavor(self, flavor_id):
650 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
651 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100652 try:
653 self._reload_connection()
654 flavor = self.nova.flavors.find(id=flavor_id)
655 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200656 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000657 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200658 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100659
tiernocf157a82017-01-30 14:07:06 +0100660 def get_flavor_id_from_data(self, flavor_dict):
661 """Obtain flavor id that match the flavor description
662 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200663 flavor_dict: contains the required ram, vcpus, disk
664 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
665 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
666 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100667 """
tiernoe26fc7a2017-05-30 14:43:03 +0200668 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100669 try:
670 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200671 flavor_candidate_id = None
672 flavor_candidate_data = (10000, 10000, 10000)
673 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
674 # numa=None
675 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100676 if numas:
677 #TODO
678 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
679 # if len(numas) > 1:
680 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
681 # numa=numas[0]
682 # numas = extended.get("numas")
683 for flavor in self.nova.flavors.list():
684 epa = flavor.get_keys()
685 if epa:
686 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200687 # TODO
688 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
689 if flavor_data == flavor_target:
690 return flavor.id
691 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
692 flavor_candidate_id = flavor.id
693 flavor_candidate_data = flavor_data
694 if not exact_match and flavor_candidate_id:
695 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100696 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
697 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
698 self._format_exception(e)
699
tiernoae4a8d12016-07-08 12:30:39 +0200700 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100701 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200702 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 +0100703 Returns the flavor identifier
704 '''
tiernoae4a8d12016-07-08 12:30:39 +0200705 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100706 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200707 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100708 name_suffix = 0
anwarsc76a3ee2018-10-04 14:05:32 +0530709 try:
710 name=flavor_data['name']
711 while retry<max_retries:
712 retry+=1
713 try:
714 self._reload_connection()
715 if change_name_if_used:
716 #get used names
717 fl_names=[]
718 fl=self.nova.flavors.list()
719 for f in fl:
720 fl_names.append(f.name)
721 while name in fl_names:
722 name_suffix += 1
723 name = flavor_data['name']+"-" + str(name_suffix)
kate721d79b2017-06-24 04:21:38 -0700724
anwarsc76a3ee2018-10-04 14:05:32 +0530725 ram = flavor_data.get('ram',64)
726 vcpus = flavor_data.get('vcpus',1)
727 numa_properties=None
tierno7edb6752016-03-21 17:37:52 +0100728
anwarsc76a3ee2018-10-04 14:05:32 +0530729 extended = flavor_data.get("extended")
730 if extended:
731 numas=extended.get("numas")
732 if numas:
733 numa_nodes = len(numas)
734 if numa_nodes > 1:
735 return -1, "Can not add flavor with more than one numa"
736 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
737 numa_properties["hw:mem_page_size"] = "large"
738 numa_properties["hw:cpu_policy"] = "dedicated"
739 numa_properties["hw:numa_mempolicy"] = "strict"
740 if self.vim_type == "VIO":
741 numa_properties["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
742 numa_properties["vmware:latency_sensitivity_level"] = "high"
743 for numa in numas:
744 #overwrite ram and vcpus
745 #check if key 'memory' is present in numa else use ram value at flavor
746 if 'memory' in numa:
747 ram = numa['memory']*1024
748 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
749 if 'paired-threads' in numa:
750 vcpus = numa['paired-threads']*2
751 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
752 numa_properties["hw:cpu_thread_policy"] = "require"
753 numa_properties["hw:cpu_policy"] = "dedicated"
754 elif 'cores' in numa:
755 vcpus = numa['cores']
756 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
757 numa_properties["hw:cpu_thread_policy"] = "isolate"
758 numa_properties["hw:cpu_policy"] = "dedicated"
759 elif 'threads' in numa:
760 vcpus = numa['threads']
761 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
762 numa_properties["hw:cpu_thread_policy"] = "prefer"
763 numa_properties["hw:cpu_policy"] = "dedicated"
764 # for interface in numa.get("interfaces",() ):
765 # if interface["dedicated"]=="yes":
766 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
767 # #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 +0000768
anwarsc76a3ee2018-10-04 14:05:32 +0530769 #create flavor
770 new_flavor=self.nova.flavors.create(name,
771 ram,
772 vcpus,
773 flavor_data.get('disk',0),
774 is_public=flavor_data.get('is_public', True)
775 )
776 #add metadata
777 if numa_properties:
778 new_flavor.set_keys(numa_properties)
779 return new_flavor.id
780 except nvExceptions.Conflict as e:
781 if change_name_if_used and retry < max_retries:
782 continue
783 self._format_exception(e)
784 #except nvExceptions.BadRequest as e:
785 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError, KeyError) as e:
786 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100787
tiernoae4a8d12016-07-08 12:30:39 +0200788 def delete_flavor(self,flavor_id):
789 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100790 '''
tiernoae4a8d12016-07-08 12:30:39 +0200791 try:
792 self._reload_connection()
793 self.nova.flavors.delete(flavor_id)
794 return flavor_id
795 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000796 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200797 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100798
tiernoae4a8d12016-07-08 12:30:39 +0200799 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100800 '''
tiernoae4a8d12016-07-08 12:30:39 +0200801 Adds a tenant image to VIM. imge_dict is a dictionary with:
802 name: name
803 disk_format: qcow2, vhd, vmdk, raw (by default), ...
804 location: path or URI
805 public: "yes" or "no"
806 metadata: metadata of the image
807 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100808 '''
tiernoae4a8d12016-07-08 12:30:39 +0200809 retry=0
810 max_retries=3
811 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100812 retry+=1
813 try:
814 self._reload_connection()
815 #determine format http://docs.openstack.org/developer/glance/formats.html
816 if "disk_format" in image_dict:
817 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100818 else: #autodiscover based on extension
tierno1beea862018-07-11 15:47:37 +0200819 if image_dict['location'].endswith(".qcow2"):
tierno7edb6752016-03-21 17:37:52 +0100820 disk_format="qcow2"
tierno1beea862018-07-11 15:47:37 +0200821 elif image_dict['location'].endswith(".vhd"):
tierno7edb6752016-03-21 17:37:52 +0100822 disk_format="vhd"
tierno1beea862018-07-11 15:47:37 +0200823 elif image_dict['location'].endswith(".vmdk"):
tierno7edb6752016-03-21 17:37:52 +0100824 disk_format="vmdk"
tierno1beea862018-07-11 15:47:37 +0200825 elif image_dict['location'].endswith(".vdi"):
tierno7edb6752016-03-21 17:37:52 +0100826 disk_format="vdi"
tierno1beea862018-07-11 15:47:37 +0200827 elif image_dict['location'].endswith(".iso"):
tierno7edb6752016-03-21 17:37:52 +0100828 disk_format="iso"
tierno1beea862018-07-11 15:47:37 +0200829 elif image_dict['location'].endswith(".aki"):
tierno7edb6752016-03-21 17:37:52 +0100830 disk_format="aki"
tierno1beea862018-07-11 15:47:37 +0200831 elif image_dict['location'].endswith(".ari"):
tierno7edb6752016-03-21 17:37:52 +0100832 disk_format="ari"
tierno1beea862018-07-11 15:47:37 +0200833 elif image_dict['location'].endswith(".ami"):
tierno7edb6752016-03-21 17:37:52 +0100834 disk_format="ami"
835 else:
836 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200837 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
shashankjain3c83a212018-10-04 13:05:46 +0530838 if self.vim_type == "VIO":
839 container_format = "bare"
840 if 'container_format' in image_dict:
841 container_format = image_dict['container_format']
842 new_image = self.glance.images.create(name=image_dict['name'], container_format=container_format,
843 disk_format=disk_format)
844 else:
845 new_image = self.glance.images.create(name=image_dict['name'])
tierno1beea862018-07-11 15:47:37 +0200846 if image_dict['location'].startswith("http"):
847 # TODO there is not a method to direct download. It must be downloaded locally with requests
848 raise vimconn.vimconnNotImplemented("Cannot create image from URL")
tierno7edb6752016-03-21 17:37:52 +0100849 else: #local path
850 with open(image_dict['location']) as fimage:
tierno1beea862018-07-11 15:47:37 +0200851 self.glance.images.upload(new_image.id, fimage)
852 #new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
853 # container_format="bare", data=fimage, disk_format=disk_format)
tierno7edb6752016-03-21 17:37:52 +0100854 metadata_to_load = image_dict.get('metadata')
shashankjain3c83a212018-10-04 13:05:46 +0530855 # TODO location is a reserved word for current openstack versions. fixed for VIO please check for openstack
856 if self.vim_type == "VIO":
857 metadata_to_load['upload_location'] = image_dict['location']
858 else:
859 metadata_to_load['location'] = image_dict['location']
tierno1beea862018-07-11 15:47:37 +0200860 self.glance.images.update(new_image.id, **metadata_to_load)
tiernoae4a8d12016-07-08 12:30:39 +0200861 return new_image.id
862 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
863 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000864 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200865 if retry==max_retries:
866 continue
867 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100868 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200869 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
870 http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000871
tiernoae4a8d12016-07-08 12:30:39 +0200872 def delete_image(self, image_id):
873 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100874 '''
tiernoae4a8d12016-07-08 12:30:39 +0200875 try:
876 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +0200877 self.glance.images.delete(image_id)
tiernoae4a8d12016-07-08 12:30:39 +0200878 return image_id
shashankjain3c83a212018-10-04 13:05:46 +0530879 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, gl1Exceptions.HTTPNotFound, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200880 self._format_exception(e)
881
882 def get_image_id_from_path(self, path):
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000883 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200884 try:
885 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +0200886 images = self.glance.images.list()
tiernoae4a8d12016-07-08 12:30:39 +0200887 for image in images:
888 if image.metadata.get("location")==path:
889 return image.id
890 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000891 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200892 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000893
garciadeblasb69fa9f2016-09-28 12:04:10 +0200894 def get_image_list(self, filter_dict={}):
895 '''Obtain tenant images from VIM
896 Filter_dict can be:
897 id: image id
898 name: image name
899 checksum: image checksum
900 Returns the image list of dictionaries:
901 [{<the fields at Filter_dict plus some VIM specific>}, ...]
902 List can be empty
903 '''
904 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
905 try:
906 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +0100907 filter_dict_os = filter_dict.copy()
garciadeblasb69fa9f2016-09-28 12:04:10 +0200908 #First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +0200909 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +0200910 filtered_list = []
911 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +0200912 try:
tierno1beea862018-07-11 15:47:37 +0200913 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
914 continue
915 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
916 continue
917 if filter_dict.get("checksum") and image["checksum"] != filter_dict["checksum"]:
918 continue
919
920 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +0200921 except gl1Exceptions.HTTPNotFound:
922 pass
garciadeblasb69fa9f2016-09-28 12:04:10 +0200923 return filtered_list
924 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
925 self._format_exception(e)
926
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200927 def __wait_for_vm(self, vm_id, status):
928 """wait until vm is in the desired status and return True.
929 If the VM gets in ERROR status, return false.
930 If the timeout is reached generate an exception"""
931 elapsed_time = 0
932 while elapsed_time < server_timeout:
933 vm_status = self.nova.servers.get(vm_id).status
934 if vm_status == status:
935 return True
936 if vm_status == 'ERROR':
937 return False
tierno1df468d2018-07-06 14:25:16 +0200938 time.sleep(5)
939 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200940
941 # if we exceeded the timeout rollback
942 if elapsed_time >= server_timeout:
943 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
944 http_code=vimconn.HTTP_Request_Timeout)
945
mirabal29356312017-07-27 12:21:22 +0200946 def _get_openstack_availablity_zones(self):
947 """
948 Get from openstack availability zones available
949 :return:
950 """
951 try:
952 openstack_availability_zone = self.nova.availability_zones.list()
953 openstack_availability_zone = [str(zone.zoneName) for zone in openstack_availability_zone
954 if zone.zoneName != 'internal']
955 return openstack_availability_zone
956 except Exception as e:
957 return None
958
959 def _set_availablity_zones(self):
960 """
961 Set vim availablity zone
962 :return:
963 """
964
965 if 'availability_zone' in self.config:
966 vim_availability_zones = self.config.get('availability_zone')
967 if isinstance(vim_availability_zones, str):
968 self.availability_zone = [vim_availability_zones]
969 elif isinstance(vim_availability_zones, list):
970 self.availability_zone = vim_availability_zones
971 else:
972 self.availability_zone = self._get_openstack_availablity_zones()
973
tierno5a3273c2017-08-29 11:43:46 +0200974 def _get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
mirabal29356312017-07-27 12:21:22 +0200975 """
tierno5a3273c2017-08-29 11:43:46 +0200976 Return thge availability zone to be used by the created VM.
977 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +0200978 """
tierno5a3273c2017-08-29 11:43:46 +0200979 if availability_zone_index is None:
980 if not self.config.get('availability_zone'):
981 return None
982 elif isinstance(self.config.get('availability_zone'), str):
983 return self.config['availability_zone']
984 else:
985 # TODO consider using a different parameter at config for default AV and AV list match
986 return self.config['availability_zone'][0]
mirabal29356312017-07-27 12:21:22 +0200987
tierno5a3273c2017-08-29 11:43:46 +0200988 vim_availability_zones = self.availability_zone
989 # check if VIM offer enough availability zones describe in the VNFD
990 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
991 # check if all the names of NFV AV match VIM AV names
992 match_by_index = False
993 for av in availability_zone_list:
994 if av not in vim_availability_zones:
995 match_by_index = True
996 break
997 if match_by_index:
998 return vim_availability_zones[availability_zone_index]
999 else:
1000 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +02001001 else:
tierno5a3273c2017-08-29 11:43:46 +02001002 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
mirabal29356312017-07-27 12:21:22 +02001003
tierno5a3273c2017-08-29 11:43:46 +02001004 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
1005 availability_zone_index=None, availability_zone_list=None):
tierno98e909c2017-10-14 13:27:03 +02001006 """Adds a VM instance to VIM
tierno7edb6752016-03-21 17:37:52 +01001007 Params:
1008 start: indicates if VM must start or boot in pause mode. Ignored
1009 image_id,flavor_id: iamge and flavor uuid
1010 net_list: list of interfaces, each one is a dictionary with:
1011 name:
1012 net_id: network uuid to connect
1013 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
1014 model: interface model, ignored #TODO
1015 mac_address: used for SR-IOV ifaces #TODO for other types
1016 use: 'data', 'bridge', 'mgmt'
tierno66eba6e2017-11-10 17:09:18 +01001017 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
tierno7edb6752016-03-21 17:37:52 +01001018 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +05001019 floating_ip: True/False (or it can be None)
tierno41a69812018-02-16 14:34:33 +01001020 'cloud_config': (optional) dictionary with:
1021 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
1022 'users': (optional) list of users to be inserted, each item is a dict with:
1023 'name': (mandatory) user name,
1024 'key-pairs': (optional) list of strings with the public key to be inserted to the user
1025 'user-data': (optional) string is a text script to be passed directly to cloud-init
1026 'config-files': (optional). List of files to be transferred. Each item is a dict with:
1027 'dest': (mandatory) string with the destination absolute path
1028 'encoding': (optional, by default text). Can be one of:
1029 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
1030 'content' (mandatory): string with the content of the file
1031 'permissions': (optional) string with file permissions, typically octal notation '0644'
1032 'owner': (optional) file owner, string with the format 'owner:group'
1033 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
mirabal29356312017-07-27 12:21:22 +02001034 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
1035 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
1036 'size': (mandatory) string with the size of the disk in GB
tierno1df468d2018-07-06 14:25:16 +02001037 'vim_id' (optional) should use this existing volume id
tierno5a3273c2017-08-29 11:43:46 +02001038 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
1039 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
1040 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01001041 #TODO ip, security groups
tierno98e909c2017-10-14 13:27:03 +02001042 Returns a tuple with the instance identifier and created_items or raises an exception on error
1043 created_items can be None or a dictionary where this method can include key-values that will be passed to
1044 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
1045 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
1046 as not present.
1047 """
tiernofa51c202017-01-27 14:58:17 +01001048 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 +01001049 try:
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001050 server = None
tierno98e909c2017-10-14 13:27:03 +02001051 created_items = {}
tiernob0b9dab2017-10-14 14:25:20 +02001052 # metadata = {}
tierno98e909c2017-10-14 13:27:03 +02001053 net_list_vim = []
1054 external_network = [] # list of external networks to be connected to instance, later on used to create floating_ip
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001055 no_secured_ports = [] # List of port-is with port-security disabled
tierno7edb6752016-03-21 17:37:52 +01001056 self._reload_connection()
tiernob0b9dab2017-10-14 14:25:20 +02001057 # metadata_vpci = {} # For a specific neutron plugin
tiernob84cbdc2017-07-07 14:30:30 +02001058 block_device_mapping = None
tierno7edb6752016-03-21 17:37:52 +01001059 for net in net_list:
tierno98e909c2017-10-14 13:27:03 +02001060 if not net.get("net_id"): # skip non connected iface
tierno7edb6752016-03-21 17:37:52 +01001061 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001062
1063 port_dict={
1064 "network_id": net["net_id"],
1065 "name": net.get("name"),
1066 "admin_state_up": True
1067 }
1068 if net["type"]=="virtual":
tiernob0b9dab2017-10-14 14:25:20 +02001069 pass
1070 # if "vpci" in net:
1071 # metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
tierno66eba6e2017-11-10 17:09:18 +01001072 elif net["type"] == "VF" or net["type"] == "SR-IOV": # for VF
tiernob0b9dab2017-10-14 14:25:20 +02001073 # if "vpci" in net:
1074 # if "VF" not in metadata_vpci:
1075 # metadata_vpci["VF"]=[]
1076 # metadata_vpci["VF"].append([ net["vpci"], "" ])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001077 port_dict["binding:vnic_type"]="direct"
tiernob0b9dab2017-10-14 14:25:20 +02001078 # VIO specific Changes
kate721d79b2017-06-24 04:21:38 -07001079 if self.vim_type == "VIO":
tiernob0b9dab2017-10-14 14:25:20 +02001080 # Need to create port with port_security_enabled = False and no-security-groups
kate721d79b2017-06-24 04:21:38 -07001081 port_dict["port_security_enabled"]=False
1082 port_dict["provider_security_groups"]=[]
1083 port_dict["security_groups"]=[]
tierno66eba6e2017-11-10 17:09:18 +01001084 else: # For PT PCI-PASSTHROUGH
tiernob0b9dab2017-10-14 14:25:20 +02001085 # VIO specific Changes
1086 # Current VIO release does not support port with type 'direct-physical'
1087 # So no need to create virtual port in case of PCI-device.
1088 # Will update port_dict code when support gets added in next VIO release
kate721d79b2017-06-24 04:21:38 -07001089 if self.vim_type == "VIO":
tiernob0b9dab2017-10-14 14:25:20 +02001090 raise vimconn.vimconnNotSupportedException(
1091 "Current VIO release does not support full passthrough (PT)")
1092 # if "vpci" in net:
1093 # if "PF" not in metadata_vpci:
1094 # metadata_vpci["PF"]=[]
1095 # metadata_vpci["PF"].append([ net["vpci"], "" ])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001096 port_dict["binding:vnic_type"]="direct-physical"
1097 if not port_dict["name"]:
1098 port_dict["name"]=name
1099 if net.get("mac_address"):
1100 port_dict["mac_address"]=net["mac_address"]
tierno41a69812018-02-16 14:34:33 +01001101 if net.get("ip_address"):
1102 port_dict["fixed_ips"] = [{'ip_address': net["ip_address"]}]
1103 # TODO add 'subnet_id': <subnet_id>
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001104 new_port = self.neutron.create_port({"port": port_dict })
tierno00e3df72017-11-29 17:20:13 +01001105 created_items["port:" + str(new_port["port"]["id"])] = True
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001106 net["mac_adress"] = new_port["port"]["mac_address"]
1107 net["vim_id"] = new_port["port"]["id"]
tiernob84cbdc2017-07-07 14:30:30 +02001108 # if try to use a network without subnetwork, it will return a emtpy list
1109 fixed_ips = new_port["port"].get("fixed_ips")
1110 if fixed_ips:
1111 net["ip"] = fixed_ips[0].get("ip_address")
1112 else:
1113 net["ip"] = None
montesmoreno994a29d2017-08-22 11:23:06 +02001114
1115 port = {"port-id": new_port["port"]["id"]}
1116 if float(self.nova.api_version.get_string()) >= 2.32:
1117 port["tag"] = new_port["port"]["name"]
1118 net_list_vim.append(port)
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001119
ahmadsaf853d452016-12-22 11:33:47 +05001120 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +01001121 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +05001122 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +01001123 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
1124 net['exit_on_floating_ip_error'] = False
1125 external_network.append(net)
tierno326fd5e2018-02-22 11:58:59 +01001126 net['floating_ip'] = self.config.get('use_floating_ip')
tiernof8383b82017-01-18 15:49:48 +01001127
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001128 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1129 # As a workaround we wait until the VM is active and then disable the port-security
tierno4d1ce222018-04-06 10:41:06 +02001130 if net.get("port_security") == False and not self.config.get("no_port_security_extension"):
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001131 no_secured_ports.append(new_port["port"]["id"])
1132
tiernob0b9dab2017-10-14 14:25:20 +02001133 # if metadata_vpci:
1134 # metadata = {"pci_assignement": json.dumps(metadata_vpci)}
1135 # if len(metadata["pci_assignement"]) >255:
1136 # #limit the metadata size
1137 # #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1138 # self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
1139 # metadata = {}
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001140
tiernob0b9dab2017-10-14 14:25:20 +02001141 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s'",
1142 name, image_id, flavor_id, str(net_list_vim), description)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001143
tiernob0b9dab2017-10-14 14:25:20 +02001144 security_groups = self.config.get('security_groups')
tierno7edb6752016-03-21 17:37:52 +01001145 if type(security_groups) is str:
1146 security_groups = ( security_groups, )
tierno98e909c2017-10-14 13:27:03 +02001147 # cloud config
tierno0a1437e2017-10-02 00:17:43 +02001148 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00001149
tierno98e909c2017-10-14 13:27:03 +02001150 # Create additional volumes in case these are present in disk_list
montesmoreno0c8def02016-12-22 12:16:23 +00001151 base_disk_index = ord('b')
tierno1df468d2018-07-06 14:25:16 +02001152 if disk_list:
tiernob84cbdc2017-07-07 14:30:30 +02001153 block_device_mapping = {}
montesmoreno0c8def02016-12-22 12:16:23 +00001154 for disk in disk_list:
tierno1df468d2018-07-06 14:25:16 +02001155 if disk.get('vim_id'):
1156 block_device_mapping['_vd' + chr(base_disk_index)] = disk['vim_id']
montesmoreno0c8def02016-12-22 12:16:23 +00001157 else:
tierno1df468d2018-07-06 14:25:16 +02001158 if 'image_id' in disk:
1159 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1160 chr(base_disk_index), imageRef=disk['image_id'])
1161 else:
1162 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1163 chr(base_disk_index))
1164 created_items["volume:" + str(volume.id)] = True
1165 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
montesmoreno0c8def02016-12-22 12:16:23 +00001166 base_disk_index += 1
1167
tierno1df468d2018-07-06 14:25:16 +02001168 # Wait until created volumes are with status available
montesmoreno0c8def02016-12-22 12:16:23 +00001169 elapsed_time = 0
tierno1df468d2018-07-06 14:25:16 +02001170 while elapsed_time < volume_timeout:
1171 for created_item in created_items:
1172 v, _, volume_id = created_item.partition(":")
1173 if v == 'volume':
1174 if self.cinder.volumes.get(volume_id).status != 'available':
1175 break
1176 else: # all ready: break from while
1177 break
1178 time.sleep(5)
1179 elapsed_time += 5
tiernob0b9dab2017-10-14 14:25:20 +02001180 # If we exceeded the timeout rollback
montesmoreno0c8def02016-12-22 12:16:23 +00001181 if elapsed_time >= volume_timeout:
montesmoreno0c8def02016-12-22 12:16:23 +00001182 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
1183 http_code=vimconn.HTTP_Request_Timeout)
mirabal29356312017-07-27 12:21:22 +02001184 # get availability Zone
tierno5a3273c2017-08-29 11:43:46 +02001185 vm_av_zone = self._get_vm_availability_zone(availability_zone_index, availability_zone_list)
montesmoreno0c8def02016-12-22 12:16:23 +00001186
tiernob0b9dab2017-10-14 14:25:20 +02001187 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
mirabal29356312017-07-27 12:21:22 +02001188 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
tiernob0b9dab2017-10-14 14:25:20 +02001189 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
mirabal29356312017-07-27 12:21:22 +02001190 security_groups, vm_av_zone, self.config.get('keypair'),
tiernob0b9dab2017-10-14 14:25:20 +02001191 userdata, config_drive, block_device_mapping))
1192 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim,
montesmoreno0c8def02016-12-22 12:16:23 +00001193 security_groups=security_groups,
mirabal29356312017-07-27 12:21:22 +02001194 availability_zone=vm_av_zone,
montesmoreno0c8def02016-12-22 12:16:23 +00001195 key_name=self.config.get('keypair'),
1196 userdata=userdata,
tiernob84cbdc2017-07-07 14:30:30 +02001197 config_drive=config_drive,
1198 block_device_mapping=block_device_mapping
montesmoreno0c8def02016-12-22 12:16:23 +00001199 ) # , description=description)
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001200
tierno326fd5e2018-02-22 11:58:59 +01001201 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001202 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1203 if no_secured_ports:
1204 self.__wait_for_vm(server.id, 'ACTIVE')
1205
1206 for port_id in no_secured_ports:
1207 try:
tierno4d1ce222018-04-06 10:41:06 +02001208 self.neutron.update_port(port_id,
1209 {"port": {"port_security_enabled": False, "security_groups": None}})
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001210 except Exception as e:
tierno4d1ce222018-04-06 10:41:06 +02001211 raise vimconn.vimconnException("It was not possible to disable port security for port {}".format(
1212 port_id))
tierno98e909c2017-10-14 13:27:03 +02001213 # print "DONE :-)", server
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001214
tierno4d1ce222018-04-06 10:41:06 +02001215 # pool_id = None
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001216 if external_network:
tierno98e909c2017-10-14 13:27:03 +02001217 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
ahmadsaf853d452016-12-22 11:33:47 +05001218 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +01001219 try:
tiernof8383b82017-01-18 15:49:48 +01001220 assigned = False
tierno98e909c2017-10-14 13:27:03 +02001221 while not assigned:
tiernof8383b82017-01-18 15:49:48 +01001222 if floating_ips:
1223 ip = floating_ips.pop(0)
tierno326fd5e2018-02-22 11:58:59 +01001224 if ip.get("port_id", False) or ip.get('tenant_id') != server.tenant_id:
1225 continue
1226 if isinstance(floating_network['floating_ip'], str):
1227 if ip.get("floating_network_id") != floating_network['floating_ip']:
1228 continue
1229 free_floating_ip = ip.get("floating_ip_address")
tiernof8383b82017-01-18 15:49:48 +01001230 else:
tiernocb3cca22018-05-31 15:08:52 +02001231 if isinstance(floating_network['floating_ip'], str) and \
1232 floating_network['floating_ip'].lower() != "true":
tierno326fd5e2018-02-22 11:58:59 +01001233 pool_id = floating_network['floating_ip']
1234 else:
tierno4d1ce222018-04-06 10:41:06 +02001235 # Find the external network
tierno326fd5e2018-02-22 11:58:59 +01001236 external_nets = list()
1237 for net in self.neutron.list_networks()['networks']:
1238 if net['router:external']:
1239 external_nets.append(net)
tiernof8383b82017-01-18 15:49:48 +01001240
tierno326fd5e2018-02-22 11:58:59 +01001241 if len(external_nets) == 0:
1242 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
1243 "network is present",
1244 http_code=vimconn.HTTP_Conflict)
1245 if len(external_nets) > 1:
1246 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
1247 "external networks are present",
1248 http_code=vimconn.HTTP_Conflict)
tiernof8383b82017-01-18 15:49:48 +01001249
tierno326fd5e2018-02-22 11:58:59 +01001250 pool_id = external_nets[0].get('id')
tiernof8383b82017-01-18 15:49:48 +01001251 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +05001252 try:
tierno4d1ce222018-04-06 10:41:06 +02001253 # self.logger.debug("Creating floating IP")
tiernof8383b82017-01-18 15:49:48 +01001254 new_floating_ip = self.neutron.create_floatingip(param)
1255 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +05001256 except Exception as e:
tierno326fd5e2018-02-22 11:58:59 +01001257 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create new floating_ip " +
1258 str(e), http_code=vimconn.HTTP_Conflict)
1259
1260 fix_ip = floating_network.get('ip')
1261 while not assigned:
1262 try:
1263 server.add_floating_ip(free_floating_ip, fix_ip)
1264 assigned = True
1265 except Exception as e:
tierno4d1ce222018-04-06 10:41:06 +02001266 # openstack need some time after VM creation to asign an IP. So retry if fails
tierno326fd5e2018-02-22 11:58:59 +01001267 vm_status = self.nova.servers.get(server.id).status
1268 if vm_status != 'ACTIVE' and vm_status != 'ERROR':
1269 if time.time() - vm_start_time < server_timeout:
1270 time.sleep(5)
1271 continue
tierno4d1ce222018-04-06 10:41:06 +02001272 raise vimconn.vimconnException(
1273 "Cannot create floating_ip: {} {}".format(type(e).__name__, e),
1274 http_code=vimconn.HTTP_Conflict)
tierno326fd5e2018-02-22 11:58:59 +01001275
tiernof8383b82017-01-18 15:49:48 +01001276 except Exception as e:
1277 if not floating_network['exit_on_floating_ip_error']:
1278 self.logger.warn("Cannot create floating_ip. %s", str(e))
1279 continue
tiernof8383b82017-01-18 15:49:48 +01001280 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001281
tierno98e909c2017-10-14 13:27:03 +02001282 return server.id, created_items
tierno7edb6752016-03-21 17:37:52 +01001283# except nvExceptions.NotFound as e:
1284# error_value=-vimconn.HTTP_Not_Found
1285# error_text= "vm instance %s not found" % vm_id
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001286# except TypeError as e:
1287# raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1288
1289 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02001290 server_id = None
1291 if server:
1292 server_id = server.id
1293 try:
1294 self.delete_vminstance(server_id, created_items)
1295 except Exception as e2:
1296 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001297
tiernoae4a8d12016-07-08 12:30:39 +02001298 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001299
tiernoae4a8d12016-07-08 12:30:39 +02001300 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +01001301 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001302 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +01001303 try:
1304 self._reload_connection()
1305 server = self.nova.servers.find(id=vm_id)
1306 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +02001307 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +00001308 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001309 self._format_exception(e)
1310
1311 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +01001312 '''
1313 Get a console for the virtual machine
1314 Params:
1315 vm_id: uuid of the VM
1316 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001317 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01001318 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001319 Returns dict with the console parameters:
1320 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001321 server: usually ip address
1322 port: the http, ssh, ... port
1323 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001324 '''
tiernoae4a8d12016-07-08 12:30:39 +02001325 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001326 try:
1327 self._reload_connection()
1328 server = self.nova.servers.find(id=vm_id)
1329 if console_type == None or console_type == "novnc":
1330 console_dict = server.get_vnc_console("novnc")
1331 elif console_type == "xvpvnc":
1332 console_dict = server.get_vnc_console(console_type)
1333 elif console_type == "rdp-html5":
1334 console_dict = server.get_rdp_console(console_type)
1335 elif console_type == "spice-html5":
1336 console_dict = server.get_spice_console(console_type)
1337 else:
tiernoae4a8d12016-07-08 12:30:39 +02001338 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001339
tierno7edb6752016-03-21 17:37:52 +01001340 console_dict1 = console_dict.get("console")
1341 if console_dict1:
1342 console_url = console_dict1.get("url")
1343 if console_url:
1344 #parse console_url
1345 protocol_index = console_url.find("//")
1346 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1347 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1348 if protocol_index < 0 or port_index<0 or suffix_index<0:
1349 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1350 console_dict={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001351 "server": console_url[protocol_index+2:port_index],
1352 "port": console_url[port_index:suffix_index],
1353 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001354 }
1355 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001356 return console_dict
1357 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001358
tierno8e995ce2016-09-22 08:13:00 +00001359 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001360 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001361
tierno98e909c2017-10-14 13:27:03 +02001362 def delete_vminstance(self, vm_id, created_items=None):
tiernoae4a8d12016-07-08 12:30:39 +02001363 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001364 '''
tiernoae4a8d12016-07-08 12:30:39 +02001365 #print "osconnector: Getting VM from VIM"
tierno98e909c2017-10-14 13:27:03 +02001366 if created_items == None:
1367 created_items = {}
tierno7edb6752016-03-21 17:37:52 +01001368 try:
1369 self._reload_connection()
tierno98e909c2017-10-14 13:27:03 +02001370 # delete VM ports attached to this networks before the virtual machine
1371 for k, v in created_items.items():
1372 if not v: # skip already deleted
1373 continue
tierno7edb6752016-03-21 17:37:52 +01001374 try:
tiernoad6bdd42018-01-10 10:43:46 +01001375 k_item, _, k_id = k.partition(":")
1376 if k_item == "port":
1377 self.neutron.delete_port(k_id)
tierno7edb6752016-03-21 17:37:52 +01001378 except Exception as e:
tierno00e3df72017-11-29 17:20:13 +01001379 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
montesmoreno0c8def02016-12-22 12:16:23 +00001380
tierno98e909c2017-10-14 13:27:03 +02001381 # #commented because detaching the volumes makes the servers.delete not work properly ?!?
1382 # #dettach volumes attached
1383 # server = self.nova.servers.get(vm_id)
1384 # volumes_attached_dict = server._info['os-extended-volumes:volumes_attached'] #volume['id']
1385 # #for volume in volumes_attached_dict:
1386 # # self.cinder.volumes.detach(volume['id'])
montesmoreno0c8def02016-12-22 12:16:23 +00001387
tierno98e909c2017-10-14 13:27:03 +02001388 if vm_id:
1389 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001390
tierno98e909c2017-10-14 13:27:03 +02001391 # delete volumes. Although having detached, they should have in active status before deleting
1392 # we ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00001393 keep_waiting = True
1394 elapsed_time = 0
1395 while keep_waiting and elapsed_time < volume_timeout:
1396 keep_waiting = False
tierno98e909c2017-10-14 13:27:03 +02001397 for k, v in created_items.items():
1398 if not v: # skip already deleted
1399 continue
1400 try:
tiernoad6bdd42018-01-10 10:43:46 +01001401 k_item, _, k_id = k.partition(":")
1402 if k_item == "volume":
1403 if self.cinder.volumes.get(k_id).status != 'available':
tierno98e909c2017-10-14 13:27:03 +02001404 keep_waiting = True
1405 else:
tiernoad6bdd42018-01-10 10:43:46 +01001406 self.cinder.volumes.delete(k_id)
tierno98e909c2017-10-14 13:27:03 +02001407 except Exception as e:
tierno00e3df72017-11-29 17:20:13 +01001408 self.logger.error("Error deleting volume: {}: {}".format(type(e).__name__, e))
montesmoreno0c8def02016-12-22 12:16:23 +00001409 if keep_waiting:
1410 time.sleep(1)
1411 elapsed_time += 1
tierno98e909c2017-10-14 13:27:03 +02001412 return None
tierno8e995ce2016-09-22 08:13:00 +00001413 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001414 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001415
tiernoae4a8d12016-07-08 12:30:39 +02001416 def refresh_vms_status(self, vm_list):
1417 '''Get the status of the virtual machines and their interfaces/ports
1418 Params: the list of VM identifiers
1419 Returns a dictionary with:
1420 vm_id: #VIM id of this Virtual Machine
1421 status: #Mandatory. Text with one of:
1422 # DELETED (not found at vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001423 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
tiernoae4a8d12016-07-08 12:30:39 +02001424 # OTHER (Vim reported other status not understood)
1425 # ERROR (VIM indicates an ERROR status)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001426 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
tiernoae4a8d12016-07-08 12:30:39 +02001427 # CREATING (on building process), ERROR
1428 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1429 #
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001430 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
tiernoae4a8d12016-07-08 12:30:39 +02001431 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1432 interfaces:
1433 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1434 mac_address: #Text format XX:XX:XX:XX:XX:XX
1435 vim_net_id: #network id where this interface is connected
1436 vim_interface_id: #interface/port VIM id
1437 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001438 compute_node: #identification of compute node where PF,VF interface is allocated
1439 pci: #PCI address of the NIC that hosts the PF,VF
1440 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001441 '''
tiernoae4a8d12016-07-08 12:30:39 +02001442 vm_dict={}
1443 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1444 for vm_id in vm_list:
1445 vm={}
1446 try:
1447 vm_vim = self.get_vminstance(vm_id)
1448 if vm_vim['status'] in vmStatus2manoFormat:
1449 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001450 else:
tiernoae4a8d12016-07-08 12:30:39 +02001451 vm['status'] = "OTHER"
1452 vm['error_msg'] = "VIM status reported " + vm_vim['status']
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001453
1454 vm['vim_info'] = self.serialize(vm_vim)
1455
tiernoae4a8d12016-07-08 12:30:39 +02001456 vm["interfaces"] = []
1457 if vm_vim.get('fault'):
1458 vm['error_msg'] = str(vm_vim['fault'])
1459 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001460 try:
tiernoae4a8d12016-07-08 12:30:39 +02001461 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02001462 port_dict = self.neutron.list_ports(device_id=vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02001463 for port in port_dict["ports"]:
1464 interface={}
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001465 interface['vim_info'] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02001466 interface["mac_address"] = port.get("mac_address")
1467 interface["vim_net_id"] = port["network_id"]
1468 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001469 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04001470 # in case of non-admin credentials, it will be missing
1471 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1472 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001473 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001474
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001475 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04001476 # in case of non-admin credentials, it will be missing
1477 if port.get('binding:profile'):
1478 if port['binding:profile'].get('pci_slot'):
1479 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1480 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1481 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1482 pci = port['binding:profile']['pci_slot']
1483 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1484 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001485 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001486 #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 +01001487 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001488 if network['network'].get('provider:network_type') == 'vlan' and \
1489 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001490 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001491 ips=[]
1492 #look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02001493 try:
1494 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1495 if floating_ip_dict.get("floatingips"):
1496 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
1497 except Exception:
1498 pass
tierno7edb6752016-03-21 17:37:52 +01001499
tiernoae4a8d12016-07-08 12:30:39 +02001500 for subnet in port["fixed_ips"]:
1501 ips.append(subnet["ip_address"])
1502 interface["ip_address"] = ";".join(ips)
1503 vm["interfaces"].append(interface)
1504 except Exception as e:
tiernob42fd9b2018-06-20 10:44:32 +02001505 self.logger.error("Error getting vm interface information {}: {}".format(type(e).__name__, e),
1506 exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +02001507 except vimconn.vimconnNotFoundException as e:
1508 self.logger.error("Exception getting vm status: %s", str(e))
1509 vm['status'] = "DELETED"
1510 vm['error_msg'] = str(e)
1511 except vimconn.vimconnException as e:
1512 self.logger.error("Exception getting vm status: %s", str(e))
1513 vm['status'] = "VIM_ERROR"
1514 vm['error_msg'] = str(e)
1515 vm_dict[vm_id] = vm
1516 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001517
tierno98e909c2017-10-14 13:27:03 +02001518 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno7edb6752016-03-21 17:37:52 +01001519 '''Send and action over a VM instance from VIM
tierno98e909c2017-10-14 13:27:03 +02001520 Returns None or the console dict if the action was successfully sent to the VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001521 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001522 try:
1523 self._reload_connection()
1524 server = self.nova.servers.find(id=vm_id)
1525 if "start" in action_dict:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001526 if action_dict["start"]=="rebuild":
tierno7edb6752016-03-21 17:37:52 +01001527 server.rebuild()
1528 else:
1529 if server.status=="PAUSED":
1530 server.unpause()
1531 elif server.status=="SUSPENDED":
1532 server.resume()
1533 elif server.status=="SHUTOFF":
1534 server.start()
1535 elif "pause" in action_dict:
1536 server.pause()
1537 elif "resume" in action_dict:
1538 server.resume()
1539 elif "shutoff" in action_dict or "shutdown" in action_dict:
1540 server.stop()
1541 elif "forceOff" in action_dict:
1542 server.stop() #TODO
1543 elif "terminate" in action_dict:
1544 server.delete()
1545 elif "createImage" in action_dict:
1546 server.create_image()
1547 #"path":path_schema,
1548 #"description":description_schema,
1549 #"name":name_schema,
1550 #"metadata":metadata_schema,
1551 #"imageRef": id_schema,
1552 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1553 elif "rebuild" in action_dict:
1554 server.rebuild(server.image['id'])
1555 elif "reboot" in action_dict:
1556 server.reboot() #reboot_type='SOFT'
1557 elif "console" in action_dict:
1558 console_type = action_dict["console"]
1559 if console_type == None or console_type == "novnc":
1560 console_dict = server.get_vnc_console("novnc")
1561 elif console_type == "xvpvnc":
1562 console_dict = server.get_vnc_console(console_type)
1563 elif console_type == "rdp-html5":
1564 console_dict = server.get_rdp_console(console_type)
1565 elif console_type == "spice-html5":
1566 console_dict = server.get_spice_console(console_type)
1567 else:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001568 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
tiernoae4a8d12016-07-08 12:30:39 +02001569 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001570 try:
1571 console_url = console_dict["console"]["url"]
1572 #parse console_url
1573 protocol_index = console_url.find("//")
1574 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1575 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1576 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001577 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001578 console_dict2={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001579 "server": console_url[protocol_index+2 : port_index],
1580 "port": int(console_url[port_index+1 : suffix_index]),
1581 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001582 }
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001583 return console_dict2
tiernoae4a8d12016-07-08 12:30:39 +02001584 except Exception as e:
1585 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001586
tierno98e909c2017-10-14 13:27:03 +02001587 return None
tierno8e995ce2016-09-22 08:13:00 +00001588 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001589 self._format_exception(e)
1590 #TODO insert exception vimconn.HTTP_Unauthorized
1591
kate721d79b2017-06-24 04:21:38 -07001592 ####### VIO Specific Changes #########
1593 def _genrate_vlanID(self):
1594 """
1595 Method to get unused vlanID
1596 Args:
1597 None
1598 Returns:
1599 vlanID
1600 """
1601 #Get used VLAN IDs
1602 usedVlanIDs = []
1603 networks = self.get_network_list()
1604 for net in networks:
1605 if net.get('provider:segmentation_id'):
1606 usedVlanIDs.append(net.get('provider:segmentation_id'))
1607 used_vlanIDs = set(usedVlanIDs)
1608
1609 #find unused VLAN ID
1610 for vlanID_range in self.config.get('dataplane_net_vlan_range'):
1611 try:
1612 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
1613 for vlanID in xrange(start_vlanid, end_vlanid + 1):
1614 if vlanID not in used_vlanIDs:
1615 return vlanID
1616 except Exception as exp:
1617 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1618 else:
1619 raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1620 " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
1621
1622
1623 def _validate_vlan_ranges(self, dataplane_net_vlan_range):
1624 """
1625 Method to validate user given vlanID ranges
1626 Args: None
1627 Returns: None
1628 """
1629 for vlanID_range in dataplane_net_vlan_range:
1630 vlan_range = vlanID_range.replace(" ", "")
1631 #validate format
1632 vlanID_pattern = r'(\d)*-(\d)*$'
1633 match_obj = re.match(vlanID_pattern, vlan_range)
1634 if not match_obj:
1635 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1636 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range))
1637
1638 start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
1639 if start_vlanid <= 0 :
1640 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1641 "Start ID can not be zero. For VLAN "\
1642 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1643 if end_vlanid > 4094 :
1644 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1645 "End VLAN ID can not be greater than 4094. For VLAN "\
1646 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1647
1648 if start_vlanid > end_vlanid:
1649 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1650 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1651 "start_ID < end_ID ".format(vlanID_range))
1652
tiernoae4a8d12016-07-08 12:30:39 +02001653#NOT USED FUNCTIONS
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001654
tiernoae4a8d12016-07-08 12:30:39 +02001655 def new_external_port(self, port_data):
1656 #TODO openstack if needed
1657 '''Adds a external port to VIM'''
1658 '''Returns the port identifier'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001659 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1660
tiernoae4a8d12016-07-08 12:30:39 +02001661 def connect_port_network(self, port_id, network_id, admin=False):
1662 #TODO openstack if needed
1663 '''Connects a external port to a network'''
1664 '''Returns status code of the VIM response'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001665 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1666
tiernoae4a8d12016-07-08 12:30:39 +02001667 def new_user(self, user_name, user_passwd, tenant_id=None):
1668 '''Adds a new user to openstack VIM'''
1669 '''Returns the user identifier'''
1670 self.logger.debug("osconnector: Adding a new user to VIM")
1671 try:
1672 self._reload_connection()
Eduardo Sousae3c0dbc2018-09-03 11:56:07 +01001673 user=self.keystone.users.create(user_name, password=user_passwd, default_project=tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +02001674 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1675 return user.id
1676 except ksExceptions.ConnectionError as e:
1677 error_value=-vimconn.HTTP_Bad_Request
1678 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1679 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001680 error_value=-vimconn.HTTP_Bad_Request
1681 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1682 #TODO insert exception vimconn.HTTP_Unauthorized
1683 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001684 self.logger.debug("new_user " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001685 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001686
1687 def delete_user(self, user_id):
1688 '''Delete a user from openstack VIM'''
1689 '''Returns the user identifier'''
1690 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001691 print("osconnector: Deleting a user from VIM")
tiernoae4a8d12016-07-08 12:30:39 +02001692 try:
1693 self._reload_connection()
1694 self.keystone.users.delete(user_id)
1695 return 1, user_id
1696 except ksExceptions.ConnectionError as e:
1697 error_value=-vimconn.HTTP_Bad_Request
1698 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1699 except ksExceptions.NotFound as e:
1700 error_value=-vimconn.HTTP_Not_Found
1701 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1702 except ksExceptions.ClientException as e: #TODO remove
1703 error_value=-vimconn.HTTP_Bad_Request
1704 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1705 #TODO insert exception vimconn.HTTP_Unauthorized
1706 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001707 self.logger.debug("delete_tenant " + error_text)
tiernoae4a8d12016-07-08 12:30:39 +02001708 return error_value, error_text
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001709
tierno7edb6752016-03-21 17:37:52 +01001710 def get_hosts_info(self):
1711 '''Get the information of deployed hosts
1712 Returns the hosts content'''
1713 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001714 print("osconnector: Getting Host info from VIM")
tierno7edb6752016-03-21 17:37:52 +01001715 try:
1716 h_list=[]
1717 self._reload_connection()
1718 hypervisors = self.nova.hypervisors.list()
1719 for hype in hypervisors:
1720 h_list.append( hype.to_dict() )
1721 return 1, {"hosts":h_list}
1722 except nvExceptions.NotFound as e:
1723 error_value=-vimconn.HTTP_Not_Found
1724 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1725 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1726 error_value=-vimconn.HTTP_Bad_Request
1727 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1728 #TODO insert exception vimconn.HTTP_Unauthorized
1729 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001730 self.logger.debug("get_hosts_info " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001731 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001732
1733 def get_hosts(self, vim_tenant):
1734 '''Get the hosts and deployed instances
1735 Returns the hosts content'''
1736 r, hype_dict = self.get_hosts_info()
1737 if r<0:
1738 return r, hype_dict
1739 hypervisors = hype_dict["hosts"]
1740 try:
1741 servers = self.nova.servers.list()
1742 for hype in hypervisors:
1743 for server in servers:
1744 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1745 if 'vm' in hype:
1746 hype['vm'].append(server.id)
1747 else:
1748 hype['vm'] = [server.id]
1749 return 1, hype_dict
1750 except nvExceptions.NotFound as e:
1751 error_value=-vimconn.HTTP_Not_Found
1752 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1753 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1754 error_value=-vimconn.HTTP_Bad_Request
1755 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1756 #TODO insert exception vimconn.HTTP_Unauthorized
1757 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001758 self.logger.debug("get_hosts " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001759 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001760
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001761 def new_classification(self, name, ctype, definition):
1762 self.logger.debug(
1763 'Adding a new (Traffic) Classification to VIM, named %s', name)
1764 try:
1765 new_class = None
1766 self._reload_connection()
1767 if ctype not in supportedClassificationTypes:
1768 raise vimconn.vimconnNotSupportedException(
1769 'OpenStack VIM connector doesn\'t support provided '
1770 'Classification Type {}, supported ones are: '
1771 '{}'.format(ctype, supportedClassificationTypes))
1772 if not self._validate_classification(ctype, definition):
1773 raise vimconn.vimconnException(
1774 'Incorrect Classification definition '
1775 'for the type specified.')
1776 classification_dict = definition
1777 classification_dict['name'] = name
tierno7edb6752016-03-21 17:37:52 +01001778
Igor D.Ccaadc442017-11-06 12:48:48 +00001779 new_class = self.neutron.create_sfc_flow_classifier(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001780 {'flow_classifier': classification_dict})
1781 return new_class['flow_classifier']['id']
1782 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1783 neExceptions.NeutronException, ConnectionError) as e:
1784 self.logger.error(
1785 'Creation of Classification failed.')
1786 self._format_exception(e)
1787
1788 def get_classification(self, class_id):
1789 self.logger.debug(" Getting Classification %s from VIM", class_id)
1790 filter_dict = {"id": class_id}
1791 class_list = self.get_classification_list(filter_dict)
1792 if len(class_list) == 0:
1793 raise vimconn.vimconnNotFoundException(
1794 "Classification '{}' not found".format(class_id))
1795 elif len(class_list) > 1:
1796 raise vimconn.vimconnConflictException(
1797 "Found more than one Classification with this criteria")
1798 classification = class_list[0]
1799 return classification
1800
1801 def get_classification_list(self, filter_dict={}):
1802 self.logger.debug("Getting Classifications from VIM filter: '%s'",
1803 str(filter_dict))
1804 try:
tierno69b590e2018-03-13 18:52:23 +01001805 filter_dict_os = filter_dict.copy()
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001806 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001807 if self.api_version3 and "tenant_id" in filter_dict_os:
1808 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
Igor D.Ccaadc442017-11-06 12:48:48 +00001809 classification_dict = self.neutron.list_sfc_flow_classifiers(
tierno69b590e2018-03-13 18:52:23 +01001810 **filter_dict_os)
1811 classification_list = classification_dict["flow_classifiers"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001812 self.__classification_os2mano(classification_list)
1813 return classification_list
1814 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1815 neExceptions.NeutronException, ConnectionError) as e:
1816 self._format_exception(e)
1817
1818 def delete_classification(self, class_id):
1819 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
1820 try:
1821 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001822 self.neutron.delete_sfc_flow_classifier(class_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001823 return class_id
1824 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1825 ksExceptions.ClientException, neExceptions.NeutronException,
1826 ConnectionError) as e:
1827 self._format_exception(e)
1828
1829 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
1830 self.logger.debug(
1831 "Adding a new Service Function Instance to VIM, named '%s'", name)
1832 try:
1833 new_sfi = None
1834 self._reload_connection()
1835 correlation = None
1836 if sfc_encap:
Igor D.Ccaadc442017-11-06 12:48:48 +00001837 correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001838 if len(ingress_ports) != 1:
1839 raise vimconn.vimconnNotSupportedException(
1840 "OpenStack VIM connector can only have "
1841 "1 ingress port per SFI")
1842 if len(egress_ports) != 1:
1843 raise vimconn.vimconnNotSupportedException(
1844 "OpenStack VIM connector can only have "
1845 "1 egress port per SFI")
1846 sfi_dict = {'name': name,
1847 'ingress': ingress_ports[0],
1848 'egress': egress_ports[0],
1849 'service_function_parameters': {
1850 'correlation': correlation}}
Igor D.Ccaadc442017-11-06 12:48:48 +00001851 new_sfi = self.neutron.create_sfc_port_pair({'port_pair': sfi_dict})
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001852 return new_sfi['port_pair']['id']
1853 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1854 neExceptions.NeutronException, ConnectionError) as e:
1855 if new_sfi:
1856 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00001857 self.neutron.delete_sfc_port_pair(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001858 new_sfi['port_pair']['id'])
1859 except Exception:
1860 self.logger.error(
1861 'Creation of Service Function Instance failed, with '
1862 'subsequent deletion failure as well.')
1863 self._format_exception(e)
1864
1865 def get_sfi(self, sfi_id):
1866 self.logger.debug(
1867 'Getting Service Function Instance %s from VIM', sfi_id)
1868 filter_dict = {"id": sfi_id}
1869 sfi_list = self.get_sfi_list(filter_dict)
1870 if len(sfi_list) == 0:
1871 raise vimconn.vimconnNotFoundException(
1872 "Service Function Instance '{}' not found".format(sfi_id))
1873 elif len(sfi_list) > 1:
1874 raise vimconn.vimconnConflictException(
1875 'Found more than one Service Function Instance '
1876 'with this criteria')
1877 sfi = sfi_list[0]
1878 return sfi
1879
1880 def get_sfi_list(self, filter_dict={}):
1881 self.logger.debug("Getting Service Function Instances from "
1882 "VIM filter: '%s'", str(filter_dict))
1883 try:
1884 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001885 filter_dict_os = filter_dict.copy()
1886 if self.api_version3 and "tenant_id" in filter_dict_os:
1887 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
1888 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001889 sfi_list = sfi_dict["port_pairs"]
1890 self.__sfi_os2mano(sfi_list)
1891 return sfi_list
1892 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1893 neExceptions.NeutronException, ConnectionError) as e:
1894 self._format_exception(e)
1895
1896 def delete_sfi(self, sfi_id):
1897 self.logger.debug("Deleting Service Function Instance '%s' "
1898 "from VIM", sfi_id)
1899 try:
1900 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001901 self.neutron.delete_sfc_port_pair(sfi_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001902 return sfi_id
1903 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1904 ksExceptions.ClientException, neExceptions.NeutronException,
1905 ConnectionError) as e:
1906 self._format_exception(e)
1907
1908 def new_sf(self, name, sfis, sfc_encap=True):
1909 self.logger.debug("Adding a new Service Function to VIM, "
1910 "named '%s'", name)
1911 try:
1912 new_sf = None
1913 self._reload_connection()
tierno9c5c8322018-03-23 15:44:03 +01001914 # correlation = None
1915 # if sfc_encap:
1916 # correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001917 for instance in sfis:
1918 sfi = self.get_sfi(instance)
Igor D.Ccaadc442017-11-06 12:48:48 +00001919 if sfi.get('sfc_encap') != sfc_encap:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001920 raise vimconn.vimconnNotSupportedException(
1921 "OpenStack VIM connector requires all SFIs of the "
1922 "same SF to share the same SFC Encapsulation")
1923 sf_dict = {'name': name,
1924 'port_pairs': sfis}
Igor D.Ccaadc442017-11-06 12:48:48 +00001925 new_sf = self.neutron.create_sfc_port_pair_group({
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001926 'port_pair_group': sf_dict})
1927 return new_sf['port_pair_group']['id']
1928 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1929 neExceptions.NeutronException, ConnectionError) as e:
1930 if new_sf:
1931 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00001932 self.neutron.delete_sfc_port_pair_group(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001933 new_sf['port_pair_group']['id'])
1934 except Exception:
1935 self.logger.error(
1936 'Creation of Service Function failed, with '
1937 'subsequent deletion failure as well.')
1938 self._format_exception(e)
1939
1940 def get_sf(self, sf_id):
1941 self.logger.debug("Getting Service Function %s from VIM", sf_id)
1942 filter_dict = {"id": sf_id}
1943 sf_list = self.get_sf_list(filter_dict)
1944 if len(sf_list) == 0:
1945 raise vimconn.vimconnNotFoundException(
1946 "Service Function '{}' not found".format(sf_id))
1947 elif len(sf_list) > 1:
1948 raise vimconn.vimconnConflictException(
1949 "Found more than one Service Function with this criteria")
1950 sf = sf_list[0]
1951 return sf
1952
1953 def get_sf_list(self, filter_dict={}):
1954 self.logger.debug("Getting Service Function from VIM filter: '%s'",
1955 str(filter_dict))
1956 try:
1957 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001958 filter_dict_os = filter_dict.copy()
1959 if self.api_version3 and "tenant_id" in filter_dict_os:
1960 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
1961 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001962 sf_list = sf_dict["port_pair_groups"]
1963 self.__sf_os2mano(sf_list)
1964 return sf_list
1965 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1966 neExceptions.NeutronException, ConnectionError) as e:
1967 self._format_exception(e)
1968
1969 def delete_sf(self, sf_id):
1970 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
1971 try:
1972 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001973 self.neutron.delete_sfc_port_pair_group(sf_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001974 return sf_id
1975 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1976 ksExceptions.ClientException, neExceptions.NeutronException,
1977 ConnectionError) as e:
1978 self._format_exception(e)
1979
1980 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
1981 self.logger.debug("Adding a new Service Function Path to VIM, "
1982 "named '%s'", name)
1983 try:
1984 new_sfp = None
1985 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001986 # In networking-sfc the MPLS encapsulation is legacy
1987 # should be used when no full SFC Encapsulation is intended
schillinge981df9a2019-01-24 09:25:11 +01001988 correlation = 'mpls'
Igor D.Ccaadc442017-11-06 12:48:48 +00001989 if sfc_encap:
1990 correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001991 sfp_dict = {'name': name,
1992 'flow_classifiers': classifications,
1993 'port_pair_groups': sfs,
1994 'chain_parameters': {'correlation': correlation}}
1995 if spi:
1996 sfp_dict['chain_id'] = spi
Igor D.Ccaadc442017-11-06 12:48:48 +00001997 new_sfp = self.neutron.create_sfc_port_chain({'port_chain': sfp_dict})
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001998 return new_sfp["port_chain"]["id"]
1999 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2000 neExceptions.NeutronException, ConnectionError) as e:
2001 if new_sfp:
2002 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00002003 self.neutron.delete_sfc_port_chain(new_sfp['port_chain']['id'])
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002004 except Exception:
2005 self.logger.error(
2006 'Creation of Service Function Path failed, with '
2007 'subsequent deletion failure as well.')
2008 self._format_exception(e)
2009
2010 def get_sfp(self, sfp_id):
2011 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
2012 filter_dict = {"id": sfp_id}
2013 sfp_list = self.get_sfp_list(filter_dict)
2014 if len(sfp_list) == 0:
2015 raise vimconn.vimconnNotFoundException(
2016 "Service Function Path '{}' not found".format(sfp_id))
2017 elif len(sfp_list) > 1:
2018 raise vimconn.vimconnConflictException(
2019 "Found more than one Service Function Path with this criteria")
2020 sfp = sfp_list[0]
2021 return sfp
2022
2023 def get_sfp_list(self, filter_dict={}):
2024 self.logger.debug("Getting Service Function Paths from VIM filter: "
2025 "'%s'", str(filter_dict))
2026 try:
2027 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01002028 filter_dict_os = filter_dict.copy()
2029 if self.api_version3 and "tenant_id" in filter_dict_os:
2030 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
2031 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002032 sfp_list = sfp_dict["port_chains"]
2033 self.__sfp_os2mano(sfp_list)
2034 return sfp_list
2035 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2036 neExceptions.NeutronException, ConnectionError) as e:
2037 self._format_exception(e)
2038
2039 def delete_sfp(self, sfp_id):
2040 self.logger.debug(
2041 "Deleting Service Function Path '%s' from VIM", sfp_id)
2042 try:
2043 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00002044 self.neutron.delete_sfc_port_chain(sfp_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002045 return sfp_id
2046 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2047 ksExceptions.ClientException, neExceptions.NeutronException,
2048 ConnectionError) as e:
2049 self._format_exception(e)