blob: 419caa016001d7b83463aadb71b4b08b58c35622 [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:
kate721d79b2017-06-24 04:21:38 -0700231 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200232 else:
kate721d79b2017-06-24 04:21:38 -0700233 self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200234 self.session['keystone'] = self.keystone
montesmoreno9317d302017-08-16 12:48:23 +0200235 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
236 # This implementation approach is due to the warning message in
237 # https://developer.openstack.org/api-guide/compute/microversions.html
238 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
239 # always require an specific microversion.
240 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
241 version = self.config.get("microversion")
242 if not version:
243 version = "2.1"
kate54616752017-09-05 23:26:28 -0700244 self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type)
kate721d79b2017-06-24 04:21:38 -0700245 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type)
246 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type)
247 if self.endpoint_type == "internalURL":
248 glance_service_id = self.keystone.services.list(name="glance")[0].id
249 glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
250 else:
251 glance_endpoint = None
252 self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
253 #using version 1 of glance client in new_image()
tierno1beea862018-07-11 15:47:37 +0200254 # self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
255 # endpoint=glance_endpoint)
tiernob5cef372017-06-19 15:52:22 +0200256 self.session['reload_client'] = False
257 self.persistent_info['session'] = self.session
mirabal29356312017-07-27 12:21:22 +0200258 # add availablity zone info inside self.persistent_info
259 self._set_availablity_zones()
260 self.persistent_info['availability_zone'] = self.availability_zone
ahmadsa95baa272016-11-30 09:14:11 +0500261
tierno7edb6752016-03-21 17:37:52 +0100262 def __net_os2mano(self, net_list_dict):
263 '''Transform the net openstack format to mano format
264 net_list_dict can be a list of dict or a single dict'''
265 if type(net_list_dict) is dict:
266 net_list_=(net_list_dict,)
267 elif type(net_list_dict) is list:
268 net_list_=net_list_dict
269 else:
270 raise TypeError("param net_list_dict must be a list or a dictionary")
271 for net in net_list_:
272 if net.get('provider:network_type') == "vlan":
273 net['type']='data'
274 else:
275 net['type']='bridge'
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200276
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000277 def __classification_os2mano(self, class_list_dict):
278 """Transform the openstack format (Flow Classifier) to mano format
279 (Classification) class_list_dict can be a list of dict or a single dict
280 """
281 if isinstance(class_list_dict, dict):
282 class_list_ = [class_list_dict]
283 elif isinstance(class_list_dict, list):
284 class_list_ = class_list_dict
285 else:
286 raise TypeError(
287 "param class_list_dict must be a list or a dictionary")
288 for classification in class_list_:
289 id = classification.pop('id')
290 name = classification.pop('name')
291 description = classification.pop('description')
292 project_id = classification.pop('project_id')
293 tenant_id = classification.pop('tenant_id')
294 original_classification = copy.deepcopy(classification)
295 classification.clear()
296 classification['ctype'] = 'legacy_flow_classifier'
297 classification['definition'] = original_classification
298 classification['id'] = id
299 classification['name'] = name
300 classification['description'] = description
301 classification['project_id'] = project_id
302 classification['tenant_id'] = tenant_id
303
304 def __sfi_os2mano(self, sfi_list_dict):
305 """Transform the openstack format (Port Pair) to mano format (SFI)
306 sfi_list_dict can be a list of dict or a single dict
307 """
308 if isinstance(sfi_list_dict, dict):
309 sfi_list_ = [sfi_list_dict]
310 elif isinstance(sfi_list_dict, list):
311 sfi_list_ = sfi_list_dict
312 else:
313 raise TypeError(
314 "param sfi_list_dict must be a list or a dictionary")
315 for sfi in sfi_list_:
316 sfi['ingress_ports'] = []
317 sfi['egress_ports'] = []
318 if sfi.get('ingress'):
319 sfi['ingress_ports'].append(sfi['ingress'])
320 if sfi.get('egress'):
321 sfi['egress_ports'].append(sfi['egress'])
322 del sfi['ingress']
323 del sfi['egress']
324 params = sfi.get('service_function_parameters')
325 sfc_encap = False
326 if params:
327 correlation = params.get('correlation')
328 if correlation:
329 sfc_encap = True
330 sfi['sfc_encap'] = sfc_encap
331 del sfi['service_function_parameters']
332
333 def __sf_os2mano(self, sf_list_dict):
334 """Transform the openstack format (Port Pair Group) to mano format (SF)
335 sf_list_dict can be a list of dict or a single dict
336 """
337 if isinstance(sf_list_dict, dict):
338 sf_list_ = [sf_list_dict]
339 elif isinstance(sf_list_dict, list):
340 sf_list_ = sf_list_dict
341 else:
342 raise TypeError(
343 "param sf_list_dict must be a list or a dictionary")
344 for sf in sf_list_:
345 del sf['port_pair_group_parameters']
346 sf['sfis'] = sf['port_pairs']
347 del sf['port_pairs']
348
349 def __sfp_os2mano(self, sfp_list_dict):
350 """Transform the openstack format (Port Chain) to mano format (SFP)
351 sfp_list_dict can be a list of dict or a single dict
352 """
353 if isinstance(sfp_list_dict, dict):
354 sfp_list_ = [sfp_list_dict]
355 elif isinstance(sfp_list_dict, list):
356 sfp_list_ = sfp_list_dict
357 else:
358 raise TypeError(
359 "param sfp_list_dict must be a list or a dictionary")
360 for sfp in sfp_list_:
361 params = sfp.pop('chain_parameters')
362 sfc_encap = False
363 if params:
364 correlation = params.get('correlation')
365 if correlation:
366 sfc_encap = True
367 sfp['sfc_encap'] = sfc_encap
368 sfp['spi'] = sfp.pop('chain_id')
369 sfp['classifications'] = sfp.pop('flow_classifiers')
370 sfp['service_functions'] = sfp.pop('port_pair_groups')
371
372 # placeholder for now; read TODO note below
373 def _validate_classification(self, type, definition):
374 # only legacy_flow_classifier Type is supported at this point
375 return True
376 # TODO(igordcard): this method should be an abstract method of an
377 # abstract Classification class to be implemented by the specific
378 # Types. Also, abstract vimconnector should call the validation
379 # method before the implemented VIM connectors are called.
380
tiernoae4a8d12016-07-08 12:30:39 +0200381 def _format_exception(self, exception):
382 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
shashankjain3c83a212018-10-04 13:05:46 +0530383 if isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound, ksExceptions.NotFound, gl1Exceptions.HTTPNotFound)):
tiernoae4a8d12016-07-08 12:30:39 +0200384 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
shashankjain3c83a212018-10-04 13:05:46 +0530385 elif isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
386 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed)):
387 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
388 elif isinstance(exception, (KeyError, nvExceptions.BadRequest, ksExceptions.BadRequest)):
anwarsc76a3ee2018-10-04 14:05:32 +0530389 raise vimconn.vimconnException(type(exception).__name__ + ": " + str(exception))
390 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
391 neExceptions.NeutronException)):
392 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
tiernoae4a8d12016-07-08 12:30:39 +0200393 elif isinstance(exception, nvExceptions.Conflict):
394 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200395 elif isinstance(exception, vimconn.vimconnException):
tierno41a69812018-02-16 14:34:33 +0100396 raise exception
tiernof716aea2017-06-21 18:01:40 +0200397 else: # ()
tiernob84cbdc2017-07-07 14:30:30 +0200398 self.logger.error("General Exception " + str(exception), exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +0200399 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
400
401 def get_tenant_list(self, filter_dict={}):
402 '''Obtain tenants of VIM
403 filter_dict can contain the following keys:
404 name: filter by tenant name
405 id: filter by tenant uuid/id
406 <other VIM specific>
407 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
408 '''
ahmadsa95baa272016-11-30 09:14:11 +0500409 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200410 try:
411 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200412 if self.api_version3:
413 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500414 else:
tiernof716aea2017-06-21 18:01:40 +0200415 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500416 project_list=[]
417 for project in project_class_list:
tiernof716aea2017-06-21 18:01:40 +0200418 if filter_dict.get('id') and filter_dict["id"] != project.id:
419 continue
ahmadsa95baa272016-11-30 09:14:11 +0500420 project_list.append(project.to_dict())
421 return project_list
tiernof716aea2017-06-21 18:01:40 +0200422 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200423 self._format_exception(e)
424
425 def new_tenant(self, tenant_name, tenant_description):
426 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
427 self.logger.debug("Adding a new tenant name: %s", tenant_name)
428 try:
429 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200430 if self.api_version3:
431 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
432 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500433 else:
tiernof716aea2017-06-21 18:01:40 +0200434 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500435 return project.id
shashankjain3c83a212018-10-04 13:05:46 +0530436 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200437 self._format_exception(e)
438
439 def delete_tenant(self, tenant_id):
440 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
441 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
442 try:
443 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200444 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500445 self.keystone.projects.delete(tenant_id)
446 else:
447 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200448 return tenant_id
shashankjain3c83a212018-10-04 13:05:46 +0530449 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200450 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500451
garciadeblas9f8456e2016-09-05 05:02:59 +0200452 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200453 '''Adds a tenant network to VIM. Returns the network identifier'''
454 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000455 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100456 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000457 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100458 self._reload_connection()
459 network_dict = {'name': net_name, 'admin_state_up': True}
460 if net_type=="data" or net_type=="ptp":
461 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200462 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100463 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
464 network_dict["provider:network_type"] = "vlan"
465 if vlan!=None:
466 network_dict["provider:network_type"] = vlan
kate721d79b2017-06-24 04:21:38 -0700467
468 ####### VIO Specific Changes #########
469 if self.vim_type == "VIO":
470 if vlan is not None:
471 network_dict["provider:segmentation_id"] = vlan
472 else:
473 if self.config.get('dataplane_net_vlan_range') is None:
474 raise vimconn.vimconnConflictException("You must provide "\
475 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
476 "at config value before creating sriov network with vlan tag")
477
478 network_dict["provider:segmentation_id"] = self._genrate_vlanID()
479
tiernoae4a8d12016-07-08 12:30:39 +0200480 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100481 new_net=self.neutron.create_network({'network':network_dict})
482 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200483 #create subnetwork, even if there is no profile
484 if not ip_profile:
485 ip_profile = {}
tierno41a69812018-02-16 14:34:33 +0100486 if not ip_profile.get('subnet_address'):
garciadeblas2299e3b2017-01-26 14:35:55 +0000487 #Fake subnet is required
488 subnet_rand = random.randint(0, 255)
489 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000490 if 'ip_version' not in ip_profile:
garciadeblas9f8456e2016-09-05 05:02:59 +0200491 ip_profile['ip_version'] = "IPv4"
tiernoa1fb4462017-06-30 12:25:50 +0200492 subnet = {"name":net_name+"-subnet",
tierno7edb6752016-03-21 17:37:52 +0100493 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200494 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
495 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100496 }
tiernoa1fb4462017-06-30 12:25:50 +0200497 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
tierno41a69812018-02-16 14:34:33 +0100498 if ip_profile.get('gateway_address'):
tierno55d234c2018-07-04 18:29:21 +0200499 subnet['gateway_ip'] = ip_profile['gateway_address']
500 else:
501 subnet['gateway_ip'] = None
garciadeblasedca7b32016-09-29 14:01:52 +0000502 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200503 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200504 if 'dhcp_enabled' in ip_profile:
tierno41a69812018-02-16 14:34:33 +0100505 subnet['enable_dhcp'] = False if \
506 ip_profile['dhcp_enabled']=="false" or ip_profile['dhcp_enabled']==False else True
507 if ip_profile.get('dhcp_start_address'):
tiernoa1fb4462017-06-30 12:25:50 +0200508 subnet['allocation_pools'] = []
garciadeblas9f8456e2016-09-05 05:02:59 +0200509 subnet['allocation_pools'].append(dict())
510 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
tierno41a69812018-02-16 14:34:33 +0100511 if ip_profile.get('dhcp_count'):
garciadeblas9f8456e2016-09-05 05:02:59 +0200512 #parts = ip_profile['dhcp_start_address'].split('.')
513 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
514 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200515 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200516 ip_str = str(netaddr.IPAddress(ip_int))
517 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000518 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100519 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200520 return new_net["network"]["id"]
tierno41a69812018-02-16 14:34:33 +0100521 except Exception as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000522 if new_net:
523 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200524 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100525
526 def get_network_list(self, filter_dict={}):
527 '''Obtain tenant networks of VIM
528 Filter_dict can be:
529 name: network name
530 id: network uuid
531 shared: boolean
532 tenant_id: tenant
533 admin_state_up: boolean
534 status: 'ACTIVE'
535 Returns the network list of dictionaries
536 '''
tiernoae4a8d12016-07-08 12:30:39 +0200537 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100538 try:
539 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +0100540 filter_dict_os = filter_dict.copy()
541 if self.api_version3 and "tenant_id" in filter_dict_os:
542 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id') #T ODO check
543 net_dict = self.neutron.list_networks(**filter_dict_os)
tierno00e3df72017-11-29 17:20:13 +0100544 net_list = net_dict["networks"]
tierno7edb6752016-03-21 17:37:52 +0100545 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200546 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000547 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200548 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100549
tiernoae4a8d12016-07-08 12:30:39 +0200550 def get_network(self, net_id):
551 '''Obtain details of network from VIM
552 Returns the network information from a network id'''
553 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100554 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200555 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100556 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200557 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100558 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200559 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100560 net = net_list[0]
561 subnets=[]
562 for subnet_id in net.get("subnets", () ):
563 try:
564 subnet = self.neutron.show_subnet(subnet_id)
565 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200566 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
567 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100568 subnets.append(subnet)
569 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100570 net["encapsulation"] = net.get('provider:network_type')
Anderson Bravalheri0fb70282018-12-16 19:28:37 +0000571 net["encapsulation_type"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100572 net["segmentation_id"] = net.get('provider:segmentation_id')
Anderson Bravalheri0fb70282018-12-16 19:28:37 +0000573 net["encapsulation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200574 return net
tierno7edb6752016-03-21 17:37:52 +0100575
tiernoae4a8d12016-07-08 12:30:39 +0200576 def delete_network(self, net_id):
577 '''Deletes a tenant network from VIM. Returns the old network identifier'''
578 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100579 try:
580 self._reload_connection()
581 #delete VM ports attached to this networks before the network
582 ports = self.neutron.list_ports(network_id=net_id)
583 for p in ports['ports']:
584 try:
585 self.neutron.delete_port(p["id"])
586 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200587 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100588 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200589 return net_id
590 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000591 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200592 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100593
tiernoae4a8d12016-07-08 12:30:39 +0200594 def refresh_nets_status(self, net_list):
595 '''Get the status of the networks
596 Params: the list of network identifiers
597 Returns a dictionary with:
598 net_id: #VIM id of this network
599 status: #Mandatory. Text with one of:
600 # DELETED (not found at vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100601 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
tiernoae4a8d12016-07-08 12:30:39 +0200602 # OTHER (Vim reported other status not understood)
603 # ERROR (VIM indicates an ERROR status)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100604 # ACTIVE, INACTIVE, DOWN (admin down),
tiernoae4a8d12016-07-08 12:30:39 +0200605 # BUILD (on building process)
606 #
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100607 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
tiernoae4a8d12016-07-08 12:30:39 +0200608 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
609
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000610 '''
tiernoae4a8d12016-07-08 12:30:39 +0200611 net_dict={}
612 for net_id in net_list:
613 net = {}
614 try:
615 net_vim = self.get_network(net_id)
616 if net_vim['status'] in netStatus2manoFormat:
617 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
618 else:
619 net["status"] = "OTHER"
620 net["error_msg"] = "VIM status reported " + net_vim['status']
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000621
tierno8e995ce2016-09-22 08:13:00 +0000622 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200623 net['status'] = 'DOWN'
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100624
625 net['vim_info'] = self.serialize(net_vim)
626
tiernoae4a8d12016-07-08 12:30:39 +0200627 if net_vim.get('fault'): #TODO
628 net['error_msg'] = str(net_vim['fault'])
629 except vimconn.vimconnNotFoundException as e:
630 self.logger.error("Exception getting net status: %s", str(e))
631 net['status'] = "DELETED"
632 net['error_msg'] = str(e)
633 except vimconn.vimconnException as e:
634 self.logger.error("Exception getting net status: %s", str(e))
635 net['status'] = "VIM_ERROR"
636 net['error_msg'] = str(e)
637 net_dict[net_id] = net
638 return net_dict
639
640 def get_flavor(self, flavor_id):
641 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
642 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100643 try:
644 self._reload_connection()
645 flavor = self.nova.flavors.find(id=flavor_id)
646 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200647 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000648 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200649 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100650
tiernocf157a82017-01-30 14:07:06 +0100651 def get_flavor_id_from_data(self, flavor_dict):
652 """Obtain flavor id that match the flavor description
653 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200654 flavor_dict: contains the required ram, vcpus, disk
655 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
656 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
657 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100658 """
tiernoe26fc7a2017-05-30 14:43:03 +0200659 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100660 try:
661 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200662 flavor_candidate_id = None
663 flavor_candidate_data = (10000, 10000, 10000)
664 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
665 # numa=None
666 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100667 if numas:
668 #TODO
669 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
670 # if len(numas) > 1:
671 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
672 # numa=numas[0]
673 # numas = extended.get("numas")
674 for flavor in self.nova.flavors.list():
675 epa = flavor.get_keys()
676 if epa:
677 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200678 # TODO
679 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
680 if flavor_data == flavor_target:
681 return flavor.id
682 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
683 flavor_candidate_id = flavor.id
684 flavor_candidate_data = flavor_data
685 if not exact_match and flavor_candidate_id:
686 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100687 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
688 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
689 self._format_exception(e)
690
tiernoae4a8d12016-07-08 12:30:39 +0200691 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100692 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200693 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 +0100694 Returns the flavor identifier
695 '''
tiernoae4a8d12016-07-08 12:30:39 +0200696 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100697 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200698 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100699 name_suffix = 0
anwarsc76a3ee2018-10-04 14:05:32 +0530700 try:
701 name=flavor_data['name']
702 while retry<max_retries:
703 retry+=1
704 try:
705 self._reload_connection()
706 if change_name_if_used:
707 #get used names
708 fl_names=[]
709 fl=self.nova.flavors.list()
710 for f in fl:
711 fl_names.append(f.name)
712 while name in fl_names:
713 name_suffix += 1
714 name = flavor_data['name']+"-" + str(name_suffix)
kate721d79b2017-06-24 04:21:38 -0700715
anwarsc76a3ee2018-10-04 14:05:32 +0530716 ram = flavor_data.get('ram',64)
717 vcpus = flavor_data.get('vcpus',1)
718 numa_properties=None
tierno7edb6752016-03-21 17:37:52 +0100719
anwarsc76a3ee2018-10-04 14:05:32 +0530720 extended = flavor_data.get("extended")
721 if extended:
722 numas=extended.get("numas")
723 if numas:
724 numa_nodes = len(numas)
725 if numa_nodes > 1:
726 return -1, "Can not add flavor with more than one numa"
727 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
728 numa_properties["hw:mem_page_size"] = "large"
729 numa_properties["hw:cpu_policy"] = "dedicated"
730 numa_properties["hw:numa_mempolicy"] = "strict"
731 if self.vim_type == "VIO":
732 numa_properties["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
733 numa_properties["vmware:latency_sensitivity_level"] = "high"
734 for numa in numas:
735 #overwrite ram and vcpus
736 #check if key 'memory' is present in numa else use ram value at flavor
737 if 'memory' in numa:
738 ram = numa['memory']*1024
739 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
740 if 'paired-threads' in numa:
741 vcpus = numa['paired-threads']*2
742 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
743 numa_properties["hw:cpu_thread_policy"] = "require"
744 numa_properties["hw:cpu_policy"] = "dedicated"
745 elif 'cores' in numa:
746 vcpus = numa['cores']
747 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
748 numa_properties["hw:cpu_thread_policy"] = "isolate"
749 numa_properties["hw:cpu_policy"] = "dedicated"
750 elif 'threads' in numa:
751 vcpus = numa['threads']
752 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
753 numa_properties["hw:cpu_thread_policy"] = "prefer"
754 numa_properties["hw:cpu_policy"] = "dedicated"
755 # for interface in numa.get("interfaces",() ):
756 # if interface["dedicated"]=="yes":
757 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
758 # #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 +0000759
anwarsc76a3ee2018-10-04 14:05:32 +0530760 #create flavor
761 new_flavor=self.nova.flavors.create(name,
762 ram,
763 vcpus,
764 flavor_data.get('disk',0),
765 is_public=flavor_data.get('is_public', True)
766 )
767 #add metadata
768 if numa_properties:
769 new_flavor.set_keys(numa_properties)
770 return new_flavor.id
771 except nvExceptions.Conflict as e:
772 if change_name_if_used and retry < max_retries:
773 continue
774 self._format_exception(e)
775 #except nvExceptions.BadRequest as e:
776 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError, KeyError) as e:
777 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100778
tiernoae4a8d12016-07-08 12:30:39 +0200779 def delete_flavor(self,flavor_id):
780 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100781 '''
tiernoae4a8d12016-07-08 12:30:39 +0200782 try:
783 self._reload_connection()
784 self.nova.flavors.delete(flavor_id)
785 return flavor_id
786 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000787 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200788 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100789
tiernoae4a8d12016-07-08 12:30:39 +0200790 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100791 '''
tiernoae4a8d12016-07-08 12:30:39 +0200792 Adds a tenant image to VIM. imge_dict is a dictionary with:
793 name: name
794 disk_format: qcow2, vhd, vmdk, raw (by default), ...
795 location: path or URI
796 public: "yes" or "no"
797 metadata: metadata of the image
798 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100799 '''
tiernoae4a8d12016-07-08 12:30:39 +0200800 retry=0
801 max_retries=3
802 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100803 retry+=1
804 try:
805 self._reload_connection()
806 #determine format http://docs.openstack.org/developer/glance/formats.html
807 if "disk_format" in image_dict:
808 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100809 else: #autodiscover based on extension
tierno1beea862018-07-11 15:47:37 +0200810 if image_dict['location'].endswith(".qcow2"):
tierno7edb6752016-03-21 17:37:52 +0100811 disk_format="qcow2"
tierno1beea862018-07-11 15:47:37 +0200812 elif image_dict['location'].endswith(".vhd"):
tierno7edb6752016-03-21 17:37:52 +0100813 disk_format="vhd"
tierno1beea862018-07-11 15:47:37 +0200814 elif image_dict['location'].endswith(".vmdk"):
tierno7edb6752016-03-21 17:37:52 +0100815 disk_format="vmdk"
tierno1beea862018-07-11 15:47:37 +0200816 elif image_dict['location'].endswith(".vdi"):
tierno7edb6752016-03-21 17:37:52 +0100817 disk_format="vdi"
tierno1beea862018-07-11 15:47:37 +0200818 elif image_dict['location'].endswith(".iso"):
tierno7edb6752016-03-21 17:37:52 +0100819 disk_format="iso"
tierno1beea862018-07-11 15:47:37 +0200820 elif image_dict['location'].endswith(".aki"):
tierno7edb6752016-03-21 17:37:52 +0100821 disk_format="aki"
tierno1beea862018-07-11 15:47:37 +0200822 elif image_dict['location'].endswith(".ari"):
tierno7edb6752016-03-21 17:37:52 +0100823 disk_format="ari"
tierno1beea862018-07-11 15:47:37 +0200824 elif image_dict['location'].endswith(".ami"):
tierno7edb6752016-03-21 17:37:52 +0100825 disk_format="ami"
826 else:
827 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200828 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
shashankjain3c83a212018-10-04 13:05:46 +0530829 if self.vim_type == "VIO":
830 container_format = "bare"
831 if 'container_format' in image_dict:
832 container_format = image_dict['container_format']
833 new_image = self.glance.images.create(name=image_dict['name'], container_format=container_format,
834 disk_format=disk_format)
835 else:
836 new_image = self.glance.images.create(name=image_dict['name'])
tierno1beea862018-07-11 15:47:37 +0200837 if image_dict['location'].startswith("http"):
838 # TODO there is not a method to direct download. It must be downloaded locally with requests
839 raise vimconn.vimconnNotImplemented("Cannot create image from URL")
tierno7edb6752016-03-21 17:37:52 +0100840 else: #local path
841 with open(image_dict['location']) as fimage:
tierno1beea862018-07-11 15:47:37 +0200842 self.glance.images.upload(new_image.id, fimage)
843 #new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
844 # container_format="bare", data=fimage, disk_format=disk_format)
tierno7edb6752016-03-21 17:37:52 +0100845 metadata_to_load = image_dict.get('metadata')
shashankjain3c83a212018-10-04 13:05:46 +0530846 # TODO location is a reserved word for current openstack versions. fixed for VIO please check for openstack
847 if self.vim_type == "VIO":
848 metadata_to_load['upload_location'] = image_dict['location']
849 else:
850 metadata_to_load['location'] = image_dict['location']
tierno1beea862018-07-11 15:47:37 +0200851 self.glance.images.update(new_image.id, **metadata_to_load)
tiernoae4a8d12016-07-08 12:30:39 +0200852 return new_image.id
853 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
854 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000855 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200856 if retry==max_retries:
857 continue
858 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100859 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200860 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
861 http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000862
tiernoae4a8d12016-07-08 12:30:39 +0200863 def delete_image(self, image_id):
864 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100865 '''
tiernoae4a8d12016-07-08 12:30:39 +0200866 try:
867 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +0200868 self.glance.images.delete(image_id)
tiernoae4a8d12016-07-08 12:30:39 +0200869 return image_id
shashankjain3c83a212018-10-04 13:05:46 +0530870 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, gl1Exceptions.HTTPNotFound, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200871 self._format_exception(e)
872
873 def get_image_id_from_path(self, path):
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000874 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200875 try:
876 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +0200877 images = self.glance.images.list()
tiernoae4a8d12016-07-08 12:30:39 +0200878 for image in images:
879 if image.metadata.get("location")==path:
880 return image.id
881 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000882 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200883 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000884
garciadeblasb69fa9f2016-09-28 12:04:10 +0200885 def get_image_list(self, filter_dict={}):
886 '''Obtain tenant images from VIM
887 Filter_dict can be:
888 id: image id
889 name: image name
890 checksum: image checksum
891 Returns the image list of dictionaries:
892 [{<the fields at Filter_dict plus some VIM specific>}, ...]
893 List can be empty
894 '''
895 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
896 try:
897 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +0100898 filter_dict_os = filter_dict.copy()
garciadeblasb69fa9f2016-09-28 12:04:10 +0200899 #First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +0200900 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +0200901 filtered_list = []
902 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +0200903 try:
tierno1beea862018-07-11 15:47:37 +0200904 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
905 continue
906 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
907 continue
908 if filter_dict.get("checksum") and image["checksum"] != filter_dict["checksum"]:
909 continue
910
911 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +0200912 except gl1Exceptions.HTTPNotFound:
913 pass
garciadeblasb69fa9f2016-09-28 12:04:10 +0200914 return filtered_list
915 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
916 self._format_exception(e)
917
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200918 def __wait_for_vm(self, vm_id, status):
919 """wait until vm is in the desired status and return True.
920 If the VM gets in ERROR status, return false.
921 If the timeout is reached generate an exception"""
922 elapsed_time = 0
923 while elapsed_time < server_timeout:
924 vm_status = self.nova.servers.get(vm_id).status
925 if vm_status == status:
926 return True
927 if vm_status == 'ERROR':
928 return False
tierno1df468d2018-07-06 14:25:16 +0200929 time.sleep(5)
930 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200931
932 # if we exceeded the timeout rollback
933 if elapsed_time >= server_timeout:
934 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
935 http_code=vimconn.HTTP_Request_Timeout)
936
mirabal29356312017-07-27 12:21:22 +0200937 def _get_openstack_availablity_zones(self):
938 """
939 Get from openstack availability zones available
940 :return:
941 """
942 try:
943 openstack_availability_zone = self.nova.availability_zones.list()
944 openstack_availability_zone = [str(zone.zoneName) for zone in openstack_availability_zone
945 if zone.zoneName != 'internal']
946 return openstack_availability_zone
947 except Exception as e:
948 return None
949
950 def _set_availablity_zones(self):
951 """
952 Set vim availablity zone
953 :return:
954 """
955
956 if 'availability_zone' in self.config:
957 vim_availability_zones = self.config.get('availability_zone')
958 if isinstance(vim_availability_zones, str):
959 self.availability_zone = [vim_availability_zones]
960 elif isinstance(vim_availability_zones, list):
961 self.availability_zone = vim_availability_zones
962 else:
963 self.availability_zone = self._get_openstack_availablity_zones()
964
tierno5a3273c2017-08-29 11:43:46 +0200965 def _get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
mirabal29356312017-07-27 12:21:22 +0200966 """
tierno5a3273c2017-08-29 11:43:46 +0200967 Return thge availability zone to be used by the created VM.
968 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +0200969 """
tierno5a3273c2017-08-29 11:43:46 +0200970 if availability_zone_index is None:
971 if not self.config.get('availability_zone'):
972 return None
973 elif isinstance(self.config.get('availability_zone'), str):
974 return self.config['availability_zone']
975 else:
976 # TODO consider using a different parameter at config for default AV and AV list match
977 return self.config['availability_zone'][0]
mirabal29356312017-07-27 12:21:22 +0200978
tierno5a3273c2017-08-29 11:43:46 +0200979 vim_availability_zones = self.availability_zone
980 # check if VIM offer enough availability zones describe in the VNFD
981 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
982 # check if all the names of NFV AV match VIM AV names
983 match_by_index = False
984 for av in availability_zone_list:
985 if av not in vim_availability_zones:
986 match_by_index = True
987 break
988 if match_by_index:
989 return vim_availability_zones[availability_zone_index]
990 else:
991 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +0200992 else:
tierno5a3273c2017-08-29 11:43:46 +0200993 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
mirabal29356312017-07-27 12:21:22 +0200994
tierno5a3273c2017-08-29 11:43:46 +0200995 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
996 availability_zone_index=None, availability_zone_list=None):
tierno98e909c2017-10-14 13:27:03 +0200997 """Adds a VM instance to VIM
tierno7edb6752016-03-21 17:37:52 +0100998 Params:
999 start: indicates if VM must start or boot in pause mode. Ignored
1000 image_id,flavor_id: iamge and flavor uuid
1001 net_list: list of interfaces, each one is a dictionary with:
1002 name:
1003 net_id: network uuid to connect
1004 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
1005 model: interface model, ignored #TODO
1006 mac_address: used for SR-IOV ifaces #TODO for other types
1007 use: 'data', 'bridge', 'mgmt'
tierno66eba6e2017-11-10 17:09:18 +01001008 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
tierno7edb6752016-03-21 17:37:52 +01001009 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +05001010 floating_ip: True/False (or it can be None)
tierno41a69812018-02-16 14:34:33 +01001011 'cloud_config': (optional) dictionary with:
1012 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
1013 'users': (optional) list of users to be inserted, each item is a dict with:
1014 'name': (mandatory) user name,
1015 'key-pairs': (optional) list of strings with the public key to be inserted to the user
1016 'user-data': (optional) string is a text script to be passed directly to cloud-init
1017 'config-files': (optional). List of files to be transferred. Each item is a dict with:
1018 'dest': (mandatory) string with the destination absolute path
1019 'encoding': (optional, by default text). Can be one of:
1020 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
1021 'content' (mandatory): string with the content of the file
1022 'permissions': (optional) string with file permissions, typically octal notation '0644'
1023 'owner': (optional) file owner, string with the format 'owner:group'
1024 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
mirabal29356312017-07-27 12:21:22 +02001025 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
1026 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
1027 'size': (mandatory) string with the size of the disk in GB
tierno1df468d2018-07-06 14:25:16 +02001028 'vim_id' (optional) should use this existing volume id
tierno5a3273c2017-08-29 11:43:46 +02001029 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
1030 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
1031 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01001032 #TODO ip, security groups
tierno98e909c2017-10-14 13:27:03 +02001033 Returns a tuple with the instance identifier and created_items or raises an exception on error
1034 created_items can be None or a dictionary where this method can include key-values that will be passed to
1035 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
1036 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
1037 as not present.
1038 """
tiernofa51c202017-01-27 14:58:17 +01001039 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 +01001040 try:
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001041 server = None
tierno98e909c2017-10-14 13:27:03 +02001042 created_items = {}
tiernob0b9dab2017-10-14 14:25:20 +02001043 # metadata = {}
tierno98e909c2017-10-14 13:27:03 +02001044 net_list_vim = []
1045 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 +02001046 no_secured_ports = [] # List of port-is with port-security disabled
tierno7edb6752016-03-21 17:37:52 +01001047 self._reload_connection()
tiernob0b9dab2017-10-14 14:25:20 +02001048 # metadata_vpci = {} # For a specific neutron plugin
tiernob84cbdc2017-07-07 14:30:30 +02001049 block_device_mapping = None
tierno7edb6752016-03-21 17:37:52 +01001050 for net in net_list:
tierno98e909c2017-10-14 13:27:03 +02001051 if not net.get("net_id"): # skip non connected iface
tierno7edb6752016-03-21 17:37:52 +01001052 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001053
1054 port_dict={
1055 "network_id": net["net_id"],
1056 "name": net.get("name"),
1057 "admin_state_up": True
1058 }
1059 if net["type"]=="virtual":
tiernob0b9dab2017-10-14 14:25:20 +02001060 pass
1061 # if "vpci" in net:
1062 # metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
tierno66eba6e2017-11-10 17:09:18 +01001063 elif net["type"] == "VF" or net["type"] == "SR-IOV": # for VF
tiernob0b9dab2017-10-14 14:25:20 +02001064 # if "vpci" in net:
1065 # if "VF" not in metadata_vpci:
1066 # metadata_vpci["VF"]=[]
1067 # metadata_vpci["VF"].append([ net["vpci"], "" ])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001068 port_dict["binding:vnic_type"]="direct"
tiernob0b9dab2017-10-14 14:25:20 +02001069 # VIO specific Changes
kate721d79b2017-06-24 04:21:38 -07001070 if self.vim_type == "VIO":
tiernob0b9dab2017-10-14 14:25:20 +02001071 # Need to create port with port_security_enabled = False and no-security-groups
kate721d79b2017-06-24 04:21:38 -07001072 port_dict["port_security_enabled"]=False
1073 port_dict["provider_security_groups"]=[]
1074 port_dict["security_groups"]=[]
tierno66eba6e2017-11-10 17:09:18 +01001075 else: # For PT PCI-PASSTHROUGH
tiernob0b9dab2017-10-14 14:25:20 +02001076 # VIO specific Changes
1077 # Current VIO release does not support port with type 'direct-physical'
1078 # So no need to create virtual port in case of PCI-device.
1079 # Will update port_dict code when support gets added in next VIO release
kate721d79b2017-06-24 04:21:38 -07001080 if self.vim_type == "VIO":
tiernob0b9dab2017-10-14 14:25:20 +02001081 raise vimconn.vimconnNotSupportedException(
1082 "Current VIO release does not support full passthrough (PT)")
1083 # if "vpci" in net:
1084 # if "PF" not in metadata_vpci:
1085 # metadata_vpci["PF"]=[]
1086 # metadata_vpci["PF"].append([ net["vpci"], "" ])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001087 port_dict["binding:vnic_type"]="direct-physical"
1088 if not port_dict["name"]:
1089 port_dict["name"]=name
1090 if net.get("mac_address"):
1091 port_dict["mac_address"]=net["mac_address"]
tierno41a69812018-02-16 14:34:33 +01001092 if net.get("ip_address"):
1093 port_dict["fixed_ips"] = [{'ip_address': net["ip_address"]}]
1094 # TODO add 'subnet_id': <subnet_id>
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001095 new_port = self.neutron.create_port({"port": port_dict })
tierno00e3df72017-11-29 17:20:13 +01001096 created_items["port:" + str(new_port["port"]["id"])] = True
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001097 net["mac_adress"] = new_port["port"]["mac_address"]
1098 net["vim_id"] = new_port["port"]["id"]
tiernob84cbdc2017-07-07 14:30:30 +02001099 # if try to use a network without subnetwork, it will return a emtpy list
1100 fixed_ips = new_port["port"].get("fixed_ips")
1101 if fixed_ips:
1102 net["ip"] = fixed_ips[0].get("ip_address")
1103 else:
1104 net["ip"] = None
montesmoreno994a29d2017-08-22 11:23:06 +02001105
1106 port = {"port-id": new_port["port"]["id"]}
1107 if float(self.nova.api_version.get_string()) >= 2.32:
1108 port["tag"] = new_port["port"]["name"]
1109 net_list_vim.append(port)
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001110
ahmadsaf853d452016-12-22 11:33:47 +05001111 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +01001112 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +05001113 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +01001114 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
1115 net['exit_on_floating_ip_error'] = False
1116 external_network.append(net)
tierno326fd5e2018-02-22 11:58:59 +01001117 net['floating_ip'] = self.config.get('use_floating_ip')
tiernof8383b82017-01-18 15:49:48 +01001118
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001119 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1120 # As a workaround we wait until the VM is active and then disable the port-security
tierno4d1ce222018-04-06 10:41:06 +02001121 if net.get("port_security") == False and not self.config.get("no_port_security_extension"):
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001122 no_secured_ports.append(new_port["port"]["id"])
1123
tiernob0b9dab2017-10-14 14:25:20 +02001124 # if metadata_vpci:
1125 # metadata = {"pci_assignement": json.dumps(metadata_vpci)}
1126 # if len(metadata["pci_assignement"]) >255:
1127 # #limit the metadata size
1128 # #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1129 # self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
1130 # metadata = {}
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001131
tiernob0b9dab2017-10-14 14:25:20 +02001132 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s'",
1133 name, image_id, flavor_id, str(net_list_vim), description)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001134
tiernob0b9dab2017-10-14 14:25:20 +02001135 security_groups = self.config.get('security_groups')
tierno7edb6752016-03-21 17:37:52 +01001136 if type(security_groups) is str:
1137 security_groups = ( security_groups, )
tierno98e909c2017-10-14 13:27:03 +02001138 # cloud config
tierno0a1437e2017-10-02 00:17:43 +02001139 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00001140
tierno98e909c2017-10-14 13:27:03 +02001141 # Create additional volumes in case these are present in disk_list
montesmoreno0c8def02016-12-22 12:16:23 +00001142 base_disk_index = ord('b')
tierno1df468d2018-07-06 14:25:16 +02001143 if disk_list:
tiernob84cbdc2017-07-07 14:30:30 +02001144 block_device_mapping = {}
montesmoreno0c8def02016-12-22 12:16:23 +00001145 for disk in disk_list:
tierno1df468d2018-07-06 14:25:16 +02001146 if disk.get('vim_id'):
1147 block_device_mapping['_vd' + chr(base_disk_index)] = disk['vim_id']
montesmoreno0c8def02016-12-22 12:16:23 +00001148 else:
tierno1df468d2018-07-06 14:25:16 +02001149 if 'image_id' in disk:
1150 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1151 chr(base_disk_index), imageRef=disk['image_id'])
1152 else:
1153 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1154 chr(base_disk_index))
1155 created_items["volume:" + str(volume.id)] = True
1156 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
montesmoreno0c8def02016-12-22 12:16:23 +00001157 base_disk_index += 1
1158
tierno1df468d2018-07-06 14:25:16 +02001159 # Wait until created volumes are with status available
montesmoreno0c8def02016-12-22 12:16:23 +00001160 elapsed_time = 0
tierno1df468d2018-07-06 14:25:16 +02001161 while elapsed_time < volume_timeout:
1162 for created_item in created_items:
1163 v, _, volume_id = created_item.partition(":")
1164 if v == 'volume':
1165 if self.cinder.volumes.get(volume_id).status != 'available':
1166 break
1167 else: # all ready: break from while
1168 break
1169 time.sleep(5)
1170 elapsed_time += 5
tiernob0b9dab2017-10-14 14:25:20 +02001171 # If we exceeded the timeout rollback
montesmoreno0c8def02016-12-22 12:16:23 +00001172 if elapsed_time >= volume_timeout:
montesmoreno0c8def02016-12-22 12:16:23 +00001173 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
1174 http_code=vimconn.HTTP_Request_Timeout)
mirabal29356312017-07-27 12:21:22 +02001175 # get availability Zone
tierno5a3273c2017-08-29 11:43:46 +02001176 vm_av_zone = self._get_vm_availability_zone(availability_zone_index, availability_zone_list)
montesmoreno0c8def02016-12-22 12:16:23 +00001177
tiernob0b9dab2017-10-14 14:25:20 +02001178 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
mirabal29356312017-07-27 12:21:22 +02001179 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
tiernob0b9dab2017-10-14 14:25:20 +02001180 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
mirabal29356312017-07-27 12:21:22 +02001181 security_groups, vm_av_zone, self.config.get('keypair'),
tiernob0b9dab2017-10-14 14:25:20 +02001182 userdata, config_drive, block_device_mapping))
1183 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim,
montesmoreno0c8def02016-12-22 12:16:23 +00001184 security_groups=security_groups,
mirabal29356312017-07-27 12:21:22 +02001185 availability_zone=vm_av_zone,
montesmoreno0c8def02016-12-22 12:16:23 +00001186 key_name=self.config.get('keypair'),
1187 userdata=userdata,
tiernob84cbdc2017-07-07 14:30:30 +02001188 config_drive=config_drive,
1189 block_device_mapping=block_device_mapping
montesmoreno0c8def02016-12-22 12:16:23 +00001190 ) # , description=description)
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001191
tierno326fd5e2018-02-22 11:58:59 +01001192 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001193 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1194 if no_secured_ports:
1195 self.__wait_for_vm(server.id, 'ACTIVE')
1196
1197 for port_id in no_secured_ports:
1198 try:
tierno4d1ce222018-04-06 10:41:06 +02001199 self.neutron.update_port(port_id,
1200 {"port": {"port_security_enabled": False, "security_groups": None}})
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001201 except Exception as e:
tierno4d1ce222018-04-06 10:41:06 +02001202 raise vimconn.vimconnException("It was not possible to disable port security for port {}".format(
1203 port_id))
tierno98e909c2017-10-14 13:27:03 +02001204 # print "DONE :-)", server
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001205
tierno4d1ce222018-04-06 10:41:06 +02001206 # pool_id = None
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001207 if external_network:
tierno98e909c2017-10-14 13:27:03 +02001208 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
ahmadsaf853d452016-12-22 11:33:47 +05001209 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +01001210 try:
tiernof8383b82017-01-18 15:49:48 +01001211 assigned = False
tierno98e909c2017-10-14 13:27:03 +02001212 while not assigned:
tiernof8383b82017-01-18 15:49:48 +01001213 if floating_ips:
1214 ip = floating_ips.pop(0)
tierno326fd5e2018-02-22 11:58:59 +01001215 if ip.get("port_id", False) or ip.get('tenant_id') != server.tenant_id:
1216 continue
1217 if isinstance(floating_network['floating_ip'], str):
1218 if ip.get("floating_network_id") != floating_network['floating_ip']:
1219 continue
1220 free_floating_ip = ip.get("floating_ip_address")
tiernof8383b82017-01-18 15:49:48 +01001221 else:
tiernocb3cca22018-05-31 15:08:52 +02001222 if isinstance(floating_network['floating_ip'], str) and \
1223 floating_network['floating_ip'].lower() != "true":
tierno326fd5e2018-02-22 11:58:59 +01001224 pool_id = floating_network['floating_ip']
1225 else:
tierno4d1ce222018-04-06 10:41:06 +02001226 # Find the external network
tierno326fd5e2018-02-22 11:58:59 +01001227 external_nets = list()
1228 for net in self.neutron.list_networks()['networks']:
1229 if net['router:external']:
1230 external_nets.append(net)
tiernof8383b82017-01-18 15:49:48 +01001231
tierno326fd5e2018-02-22 11:58:59 +01001232 if len(external_nets) == 0:
1233 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
1234 "network is present",
1235 http_code=vimconn.HTTP_Conflict)
1236 if len(external_nets) > 1:
1237 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
1238 "external networks are present",
1239 http_code=vimconn.HTTP_Conflict)
tiernof8383b82017-01-18 15:49:48 +01001240
tierno326fd5e2018-02-22 11:58:59 +01001241 pool_id = external_nets[0].get('id')
tiernof8383b82017-01-18 15:49:48 +01001242 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +05001243 try:
tierno4d1ce222018-04-06 10:41:06 +02001244 # self.logger.debug("Creating floating IP")
tiernof8383b82017-01-18 15:49:48 +01001245 new_floating_ip = self.neutron.create_floatingip(param)
1246 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +05001247 except Exception as e:
tierno326fd5e2018-02-22 11:58:59 +01001248 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create new floating_ip " +
1249 str(e), http_code=vimconn.HTTP_Conflict)
1250
1251 fix_ip = floating_network.get('ip')
1252 while not assigned:
1253 try:
1254 server.add_floating_ip(free_floating_ip, fix_ip)
1255 assigned = True
1256 except Exception as e:
tierno4d1ce222018-04-06 10:41:06 +02001257 # openstack need some time after VM creation to asign an IP. So retry if fails
tierno326fd5e2018-02-22 11:58:59 +01001258 vm_status = self.nova.servers.get(server.id).status
1259 if vm_status != 'ACTIVE' and vm_status != 'ERROR':
1260 if time.time() - vm_start_time < server_timeout:
1261 time.sleep(5)
1262 continue
tierno4d1ce222018-04-06 10:41:06 +02001263 raise vimconn.vimconnException(
1264 "Cannot create floating_ip: {} {}".format(type(e).__name__, e),
1265 http_code=vimconn.HTTP_Conflict)
tierno326fd5e2018-02-22 11:58:59 +01001266
tiernof8383b82017-01-18 15:49:48 +01001267 except Exception as e:
1268 if not floating_network['exit_on_floating_ip_error']:
1269 self.logger.warn("Cannot create floating_ip. %s", str(e))
1270 continue
tiernof8383b82017-01-18 15:49:48 +01001271 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001272
tierno98e909c2017-10-14 13:27:03 +02001273 return server.id, created_items
tierno7edb6752016-03-21 17:37:52 +01001274# except nvExceptions.NotFound as e:
1275# error_value=-vimconn.HTTP_Not_Found
1276# error_text= "vm instance %s not found" % vm_id
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001277# except TypeError as e:
1278# raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1279
1280 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02001281 server_id = None
1282 if server:
1283 server_id = server.id
1284 try:
1285 self.delete_vminstance(server_id, created_items)
1286 except Exception as e2:
1287 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001288
tiernoae4a8d12016-07-08 12:30:39 +02001289 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001290
tiernoae4a8d12016-07-08 12:30:39 +02001291 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +01001292 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001293 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +01001294 try:
1295 self._reload_connection()
1296 server = self.nova.servers.find(id=vm_id)
1297 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +02001298 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +00001299 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001300 self._format_exception(e)
1301
1302 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +01001303 '''
1304 Get a console for the virtual machine
1305 Params:
1306 vm_id: uuid of the VM
1307 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001308 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01001309 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001310 Returns dict with the console parameters:
1311 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001312 server: usually ip address
1313 port: the http, ssh, ... port
1314 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001315 '''
tiernoae4a8d12016-07-08 12:30:39 +02001316 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001317 try:
1318 self._reload_connection()
1319 server = self.nova.servers.find(id=vm_id)
1320 if console_type == None or console_type == "novnc":
1321 console_dict = server.get_vnc_console("novnc")
1322 elif console_type == "xvpvnc":
1323 console_dict = server.get_vnc_console(console_type)
1324 elif console_type == "rdp-html5":
1325 console_dict = server.get_rdp_console(console_type)
1326 elif console_type == "spice-html5":
1327 console_dict = server.get_spice_console(console_type)
1328 else:
tiernoae4a8d12016-07-08 12:30:39 +02001329 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001330
tierno7edb6752016-03-21 17:37:52 +01001331 console_dict1 = console_dict.get("console")
1332 if console_dict1:
1333 console_url = console_dict1.get("url")
1334 if console_url:
1335 #parse console_url
1336 protocol_index = console_url.find("//")
1337 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1338 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1339 if protocol_index < 0 or port_index<0 or suffix_index<0:
1340 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1341 console_dict={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001342 "server": console_url[protocol_index+2:port_index],
1343 "port": console_url[port_index:suffix_index],
1344 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001345 }
1346 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001347 return console_dict
1348 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001349
tierno8e995ce2016-09-22 08:13:00 +00001350 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001351 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001352
tierno98e909c2017-10-14 13:27:03 +02001353 def delete_vminstance(self, vm_id, created_items=None):
tiernoae4a8d12016-07-08 12:30:39 +02001354 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001355 '''
tiernoae4a8d12016-07-08 12:30:39 +02001356 #print "osconnector: Getting VM from VIM"
tierno98e909c2017-10-14 13:27:03 +02001357 if created_items == None:
1358 created_items = {}
tierno7edb6752016-03-21 17:37:52 +01001359 try:
1360 self._reload_connection()
tierno98e909c2017-10-14 13:27:03 +02001361 # delete VM ports attached to this networks before the virtual machine
1362 for k, v in created_items.items():
1363 if not v: # skip already deleted
1364 continue
tierno7edb6752016-03-21 17:37:52 +01001365 try:
tiernoad6bdd42018-01-10 10:43:46 +01001366 k_item, _, k_id = k.partition(":")
1367 if k_item == "port":
1368 self.neutron.delete_port(k_id)
tierno7edb6752016-03-21 17:37:52 +01001369 except Exception as e:
tierno00e3df72017-11-29 17:20:13 +01001370 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
montesmoreno0c8def02016-12-22 12:16:23 +00001371
tierno98e909c2017-10-14 13:27:03 +02001372 # #commented because detaching the volumes makes the servers.delete not work properly ?!?
1373 # #dettach volumes attached
1374 # server = self.nova.servers.get(vm_id)
1375 # volumes_attached_dict = server._info['os-extended-volumes:volumes_attached'] #volume['id']
1376 # #for volume in volumes_attached_dict:
1377 # # self.cinder.volumes.detach(volume['id'])
montesmoreno0c8def02016-12-22 12:16:23 +00001378
tierno98e909c2017-10-14 13:27:03 +02001379 if vm_id:
1380 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001381
tierno98e909c2017-10-14 13:27:03 +02001382 # delete volumes. Although having detached, they should have in active status before deleting
1383 # we ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00001384 keep_waiting = True
1385 elapsed_time = 0
1386 while keep_waiting and elapsed_time < volume_timeout:
1387 keep_waiting = False
tierno98e909c2017-10-14 13:27:03 +02001388 for k, v in created_items.items():
1389 if not v: # skip already deleted
1390 continue
1391 try:
tiernoad6bdd42018-01-10 10:43:46 +01001392 k_item, _, k_id = k.partition(":")
1393 if k_item == "volume":
1394 if self.cinder.volumes.get(k_id).status != 'available':
tierno98e909c2017-10-14 13:27:03 +02001395 keep_waiting = True
1396 else:
tiernoad6bdd42018-01-10 10:43:46 +01001397 self.cinder.volumes.delete(k_id)
tierno98e909c2017-10-14 13:27:03 +02001398 except Exception as e:
tierno00e3df72017-11-29 17:20:13 +01001399 self.logger.error("Error deleting volume: {}: {}".format(type(e).__name__, e))
montesmoreno0c8def02016-12-22 12:16:23 +00001400 if keep_waiting:
1401 time.sleep(1)
1402 elapsed_time += 1
tierno98e909c2017-10-14 13:27:03 +02001403 return None
tierno8e995ce2016-09-22 08:13:00 +00001404 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001405 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001406
tiernoae4a8d12016-07-08 12:30:39 +02001407 def refresh_vms_status(self, vm_list):
1408 '''Get the status of the virtual machines and their interfaces/ports
1409 Params: the list of VM identifiers
1410 Returns a dictionary with:
1411 vm_id: #VIM id of this Virtual Machine
1412 status: #Mandatory. Text with one of:
1413 # DELETED (not found at vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001414 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
tiernoae4a8d12016-07-08 12:30:39 +02001415 # OTHER (Vim reported other status not understood)
1416 # ERROR (VIM indicates an ERROR status)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001417 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
tiernoae4a8d12016-07-08 12:30:39 +02001418 # CREATING (on building process), ERROR
1419 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1420 #
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001421 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
tiernoae4a8d12016-07-08 12:30:39 +02001422 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1423 interfaces:
1424 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1425 mac_address: #Text format XX:XX:XX:XX:XX:XX
1426 vim_net_id: #network id where this interface is connected
1427 vim_interface_id: #interface/port VIM id
1428 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001429 compute_node: #identification of compute node where PF,VF interface is allocated
1430 pci: #PCI address of the NIC that hosts the PF,VF
1431 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001432 '''
tiernoae4a8d12016-07-08 12:30:39 +02001433 vm_dict={}
1434 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1435 for vm_id in vm_list:
1436 vm={}
1437 try:
1438 vm_vim = self.get_vminstance(vm_id)
1439 if vm_vim['status'] in vmStatus2manoFormat:
1440 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001441 else:
tiernoae4a8d12016-07-08 12:30:39 +02001442 vm['status'] = "OTHER"
1443 vm['error_msg'] = "VIM status reported " + vm_vim['status']
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001444
1445 vm['vim_info'] = self.serialize(vm_vim)
1446
tiernoae4a8d12016-07-08 12:30:39 +02001447 vm["interfaces"] = []
1448 if vm_vim.get('fault'):
1449 vm['error_msg'] = str(vm_vim['fault'])
1450 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001451 try:
tiernoae4a8d12016-07-08 12:30:39 +02001452 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02001453 port_dict = self.neutron.list_ports(device_id=vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02001454 for port in port_dict["ports"]:
1455 interface={}
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001456 interface['vim_info'] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02001457 interface["mac_address"] = port.get("mac_address")
1458 interface["vim_net_id"] = port["network_id"]
1459 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001460 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04001461 # in case of non-admin credentials, it will be missing
1462 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1463 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001464 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001465
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001466 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04001467 # in case of non-admin credentials, it will be missing
1468 if port.get('binding:profile'):
1469 if port['binding:profile'].get('pci_slot'):
1470 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1471 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1472 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1473 pci = port['binding:profile']['pci_slot']
1474 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1475 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001476 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001477 #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 +01001478 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001479 if network['network'].get('provider:network_type') == 'vlan' and \
1480 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001481 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001482 ips=[]
1483 #look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02001484 try:
1485 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1486 if floating_ip_dict.get("floatingips"):
1487 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
1488 except Exception:
1489 pass
tierno7edb6752016-03-21 17:37:52 +01001490
tiernoae4a8d12016-07-08 12:30:39 +02001491 for subnet in port["fixed_ips"]:
1492 ips.append(subnet["ip_address"])
1493 interface["ip_address"] = ";".join(ips)
1494 vm["interfaces"].append(interface)
1495 except Exception as e:
tiernob42fd9b2018-06-20 10:44:32 +02001496 self.logger.error("Error getting vm interface information {}: {}".format(type(e).__name__, e),
1497 exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +02001498 except vimconn.vimconnNotFoundException as e:
1499 self.logger.error("Exception getting vm status: %s", str(e))
1500 vm['status'] = "DELETED"
1501 vm['error_msg'] = str(e)
1502 except vimconn.vimconnException as e:
1503 self.logger.error("Exception getting vm status: %s", str(e))
1504 vm['status'] = "VIM_ERROR"
1505 vm['error_msg'] = str(e)
1506 vm_dict[vm_id] = vm
1507 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001508
tierno98e909c2017-10-14 13:27:03 +02001509 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno7edb6752016-03-21 17:37:52 +01001510 '''Send and action over a VM instance from VIM
tierno98e909c2017-10-14 13:27:03 +02001511 Returns None or the console dict if the action was successfully sent to the VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001512 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001513 try:
1514 self._reload_connection()
1515 server = self.nova.servers.find(id=vm_id)
1516 if "start" in action_dict:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001517 if action_dict["start"]=="rebuild":
tierno7edb6752016-03-21 17:37:52 +01001518 server.rebuild()
1519 else:
1520 if server.status=="PAUSED":
1521 server.unpause()
1522 elif server.status=="SUSPENDED":
1523 server.resume()
1524 elif server.status=="SHUTOFF":
1525 server.start()
1526 elif "pause" in action_dict:
1527 server.pause()
1528 elif "resume" in action_dict:
1529 server.resume()
1530 elif "shutoff" in action_dict or "shutdown" in action_dict:
1531 server.stop()
1532 elif "forceOff" in action_dict:
1533 server.stop() #TODO
1534 elif "terminate" in action_dict:
1535 server.delete()
1536 elif "createImage" in action_dict:
1537 server.create_image()
1538 #"path":path_schema,
1539 #"description":description_schema,
1540 #"name":name_schema,
1541 #"metadata":metadata_schema,
1542 #"imageRef": id_schema,
1543 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1544 elif "rebuild" in action_dict:
1545 server.rebuild(server.image['id'])
1546 elif "reboot" in action_dict:
1547 server.reboot() #reboot_type='SOFT'
1548 elif "console" in action_dict:
1549 console_type = action_dict["console"]
1550 if console_type == None or console_type == "novnc":
1551 console_dict = server.get_vnc_console("novnc")
1552 elif console_type == "xvpvnc":
1553 console_dict = server.get_vnc_console(console_type)
1554 elif console_type == "rdp-html5":
1555 console_dict = server.get_rdp_console(console_type)
1556 elif console_type == "spice-html5":
1557 console_dict = server.get_spice_console(console_type)
1558 else:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001559 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
tiernoae4a8d12016-07-08 12:30:39 +02001560 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001561 try:
1562 console_url = console_dict["console"]["url"]
1563 #parse console_url
1564 protocol_index = console_url.find("//")
1565 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1566 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1567 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001568 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001569 console_dict2={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001570 "server": console_url[protocol_index+2 : port_index],
1571 "port": int(console_url[port_index+1 : suffix_index]),
1572 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001573 }
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001574 return console_dict2
tiernoae4a8d12016-07-08 12:30:39 +02001575 except Exception as e:
1576 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001577
tierno98e909c2017-10-14 13:27:03 +02001578 return None
tierno8e995ce2016-09-22 08:13:00 +00001579 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001580 self._format_exception(e)
1581 #TODO insert exception vimconn.HTTP_Unauthorized
1582
kate721d79b2017-06-24 04:21:38 -07001583 ####### VIO Specific Changes #########
1584 def _genrate_vlanID(self):
1585 """
1586 Method to get unused vlanID
1587 Args:
1588 None
1589 Returns:
1590 vlanID
1591 """
1592 #Get used VLAN IDs
1593 usedVlanIDs = []
1594 networks = self.get_network_list()
1595 for net in networks:
1596 if net.get('provider:segmentation_id'):
1597 usedVlanIDs.append(net.get('provider:segmentation_id'))
1598 used_vlanIDs = set(usedVlanIDs)
1599
1600 #find unused VLAN ID
1601 for vlanID_range in self.config.get('dataplane_net_vlan_range'):
1602 try:
1603 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
1604 for vlanID in xrange(start_vlanid, end_vlanid + 1):
1605 if vlanID not in used_vlanIDs:
1606 return vlanID
1607 except Exception as exp:
1608 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1609 else:
1610 raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1611 " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
1612
1613
1614 def _validate_vlan_ranges(self, dataplane_net_vlan_range):
1615 """
1616 Method to validate user given vlanID ranges
1617 Args: None
1618 Returns: None
1619 """
1620 for vlanID_range in dataplane_net_vlan_range:
1621 vlan_range = vlanID_range.replace(" ", "")
1622 #validate format
1623 vlanID_pattern = r'(\d)*-(\d)*$'
1624 match_obj = re.match(vlanID_pattern, vlan_range)
1625 if not match_obj:
1626 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1627 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range))
1628
1629 start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
1630 if start_vlanid <= 0 :
1631 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1632 "Start ID can not be zero. For VLAN "\
1633 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1634 if end_vlanid > 4094 :
1635 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1636 "End VLAN ID can not be greater than 4094. For VLAN "\
1637 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1638
1639 if start_vlanid > end_vlanid:
1640 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1641 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1642 "start_ID < end_ID ".format(vlanID_range))
1643
tiernoae4a8d12016-07-08 12:30:39 +02001644#NOT USED FUNCTIONS
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001645
tiernoae4a8d12016-07-08 12:30:39 +02001646 def new_external_port(self, port_data):
1647 #TODO openstack if needed
1648 '''Adds a external port to VIM'''
1649 '''Returns the port identifier'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001650 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1651
tiernoae4a8d12016-07-08 12:30:39 +02001652 def connect_port_network(self, port_id, network_id, admin=False):
1653 #TODO openstack if needed
1654 '''Connects a external port to a network'''
1655 '''Returns status code of the VIM response'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001656 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1657
tiernoae4a8d12016-07-08 12:30:39 +02001658 def new_user(self, user_name, user_passwd, tenant_id=None):
1659 '''Adds a new user to openstack VIM'''
1660 '''Returns the user identifier'''
1661 self.logger.debug("osconnector: Adding a new user to VIM")
1662 try:
1663 self._reload_connection()
Eduardo Sousae3c0dbc2018-09-03 11:56:07 +01001664 user=self.keystone.users.create(user_name, password=user_passwd, default_project=tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +02001665 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1666 return user.id
1667 except ksExceptions.ConnectionError as e:
1668 error_value=-vimconn.HTTP_Bad_Request
1669 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1670 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001671 error_value=-vimconn.HTTP_Bad_Request
1672 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1673 #TODO insert exception vimconn.HTTP_Unauthorized
1674 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001675 self.logger.debug("new_user " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001676 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001677
1678 def delete_user(self, user_id):
1679 '''Delete a user from openstack VIM'''
1680 '''Returns the user identifier'''
1681 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001682 print("osconnector: Deleting a user from VIM")
tiernoae4a8d12016-07-08 12:30:39 +02001683 try:
1684 self._reload_connection()
1685 self.keystone.users.delete(user_id)
1686 return 1, user_id
1687 except ksExceptions.ConnectionError as e:
1688 error_value=-vimconn.HTTP_Bad_Request
1689 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1690 except ksExceptions.NotFound as e:
1691 error_value=-vimconn.HTTP_Not_Found
1692 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1693 except ksExceptions.ClientException as e: #TODO remove
1694 error_value=-vimconn.HTTP_Bad_Request
1695 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1696 #TODO insert exception vimconn.HTTP_Unauthorized
1697 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001698 self.logger.debug("delete_tenant " + error_text)
tiernoae4a8d12016-07-08 12:30:39 +02001699 return error_value, error_text
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001700
tierno7edb6752016-03-21 17:37:52 +01001701 def get_hosts_info(self):
1702 '''Get the information of deployed hosts
1703 Returns the hosts content'''
1704 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001705 print("osconnector: Getting Host info from VIM")
tierno7edb6752016-03-21 17:37:52 +01001706 try:
1707 h_list=[]
1708 self._reload_connection()
1709 hypervisors = self.nova.hypervisors.list()
1710 for hype in hypervisors:
1711 h_list.append( hype.to_dict() )
1712 return 1, {"hosts":h_list}
1713 except nvExceptions.NotFound as e:
1714 error_value=-vimconn.HTTP_Not_Found
1715 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1716 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1717 error_value=-vimconn.HTTP_Bad_Request
1718 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1719 #TODO insert exception vimconn.HTTP_Unauthorized
1720 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001721 self.logger.debug("get_hosts_info " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001722 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001723
1724 def get_hosts(self, vim_tenant):
1725 '''Get the hosts and deployed instances
1726 Returns the hosts content'''
1727 r, hype_dict = self.get_hosts_info()
1728 if r<0:
1729 return r, hype_dict
1730 hypervisors = hype_dict["hosts"]
1731 try:
1732 servers = self.nova.servers.list()
1733 for hype in hypervisors:
1734 for server in servers:
1735 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1736 if 'vm' in hype:
1737 hype['vm'].append(server.id)
1738 else:
1739 hype['vm'] = [server.id]
1740 return 1, hype_dict
1741 except nvExceptions.NotFound as e:
1742 error_value=-vimconn.HTTP_Not_Found
1743 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1744 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1745 error_value=-vimconn.HTTP_Bad_Request
1746 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1747 #TODO insert exception vimconn.HTTP_Unauthorized
1748 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001749 self.logger.debug("get_hosts " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001750 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001751
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001752 def new_classification(self, name, ctype, definition):
1753 self.logger.debug(
1754 'Adding a new (Traffic) Classification to VIM, named %s', name)
1755 try:
1756 new_class = None
1757 self._reload_connection()
1758 if ctype not in supportedClassificationTypes:
1759 raise vimconn.vimconnNotSupportedException(
1760 'OpenStack VIM connector doesn\'t support provided '
1761 'Classification Type {}, supported ones are: '
1762 '{}'.format(ctype, supportedClassificationTypes))
1763 if not self._validate_classification(ctype, definition):
1764 raise vimconn.vimconnException(
1765 'Incorrect Classification definition '
1766 'for the type specified.')
1767 classification_dict = definition
1768 classification_dict['name'] = name
tierno7edb6752016-03-21 17:37:52 +01001769
Igor D.Ccaadc442017-11-06 12:48:48 +00001770 new_class = self.neutron.create_sfc_flow_classifier(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001771 {'flow_classifier': classification_dict})
1772 return new_class['flow_classifier']['id']
1773 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1774 neExceptions.NeutronException, ConnectionError) as e:
1775 self.logger.error(
1776 'Creation of Classification failed.')
1777 self._format_exception(e)
1778
1779 def get_classification(self, class_id):
1780 self.logger.debug(" Getting Classification %s from VIM", class_id)
1781 filter_dict = {"id": class_id}
1782 class_list = self.get_classification_list(filter_dict)
1783 if len(class_list) == 0:
1784 raise vimconn.vimconnNotFoundException(
1785 "Classification '{}' not found".format(class_id))
1786 elif len(class_list) > 1:
1787 raise vimconn.vimconnConflictException(
1788 "Found more than one Classification with this criteria")
1789 classification = class_list[0]
1790 return classification
1791
1792 def get_classification_list(self, filter_dict={}):
1793 self.logger.debug("Getting Classifications from VIM filter: '%s'",
1794 str(filter_dict))
1795 try:
tierno69b590e2018-03-13 18:52:23 +01001796 filter_dict_os = filter_dict.copy()
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001797 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001798 if self.api_version3 and "tenant_id" in filter_dict_os:
1799 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
Igor D.Ccaadc442017-11-06 12:48:48 +00001800 classification_dict = self.neutron.list_sfc_flow_classifiers(
tierno69b590e2018-03-13 18:52:23 +01001801 **filter_dict_os)
1802 classification_list = classification_dict["flow_classifiers"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001803 self.__classification_os2mano(classification_list)
1804 return classification_list
1805 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1806 neExceptions.NeutronException, ConnectionError) as e:
1807 self._format_exception(e)
1808
1809 def delete_classification(self, class_id):
1810 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
1811 try:
1812 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001813 self.neutron.delete_sfc_flow_classifier(class_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001814 return class_id
1815 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1816 ksExceptions.ClientException, neExceptions.NeutronException,
1817 ConnectionError) as e:
1818 self._format_exception(e)
1819
1820 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
1821 self.logger.debug(
1822 "Adding a new Service Function Instance to VIM, named '%s'", name)
1823 try:
1824 new_sfi = None
1825 self._reload_connection()
1826 correlation = None
1827 if sfc_encap:
Igor D.Ccaadc442017-11-06 12:48:48 +00001828 correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001829 if len(ingress_ports) != 1:
1830 raise vimconn.vimconnNotSupportedException(
1831 "OpenStack VIM connector can only have "
1832 "1 ingress port per SFI")
1833 if len(egress_ports) != 1:
1834 raise vimconn.vimconnNotSupportedException(
1835 "OpenStack VIM connector can only have "
1836 "1 egress port per SFI")
1837 sfi_dict = {'name': name,
1838 'ingress': ingress_ports[0],
1839 'egress': egress_ports[0],
1840 'service_function_parameters': {
1841 'correlation': correlation}}
Igor D.Ccaadc442017-11-06 12:48:48 +00001842 new_sfi = self.neutron.create_sfc_port_pair({'port_pair': sfi_dict})
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001843 return new_sfi['port_pair']['id']
1844 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1845 neExceptions.NeutronException, ConnectionError) as e:
1846 if new_sfi:
1847 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00001848 self.neutron.delete_sfc_port_pair(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001849 new_sfi['port_pair']['id'])
1850 except Exception:
1851 self.logger.error(
1852 'Creation of Service Function Instance failed, with '
1853 'subsequent deletion failure as well.')
1854 self._format_exception(e)
1855
1856 def get_sfi(self, sfi_id):
1857 self.logger.debug(
1858 'Getting Service Function Instance %s from VIM', sfi_id)
1859 filter_dict = {"id": sfi_id}
1860 sfi_list = self.get_sfi_list(filter_dict)
1861 if len(sfi_list) == 0:
1862 raise vimconn.vimconnNotFoundException(
1863 "Service Function Instance '{}' not found".format(sfi_id))
1864 elif len(sfi_list) > 1:
1865 raise vimconn.vimconnConflictException(
1866 'Found more than one Service Function Instance '
1867 'with this criteria')
1868 sfi = sfi_list[0]
1869 return sfi
1870
1871 def get_sfi_list(self, filter_dict={}):
1872 self.logger.debug("Getting Service Function Instances from "
1873 "VIM filter: '%s'", str(filter_dict))
1874 try:
1875 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001876 filter_dict_os = filter_dict.copy()
1877 if self.api_version3 and "tenant_id" in filter_dict_os:
1878 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
1879 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001880 sfi_list = sfi_dict["port_pairs"]
1881 self.__sfi_os2mano(sfi_list)
1882 return sfi_list
1883 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1884 neExceptions.NeutronException, ConnectionError) as e:
1885 self._format_exception(e)
1886
1887 def delete_sfi(self, sfi_id):
1888 self.logger.debug("Deleting Service Function Instance '%s' "
1889 "from VIM", sfi_id)
1890 try:
1891 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001892 self.neutron.delete_sfc_port_pair(sfi_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001893 return sfi_id
1894 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1895 ksExceptions.ClientException, neExceptions.NeutronException,
1896 ConnectionError) as e:
1897 self._format_exception(e)
1898
1899 def new_sf(self, name, sfis, sfc_encap=True):
1900 self.logger.debug("Adding a new Service Function to VIM, "
1901 "named '%s'", name)
1902 try:
1903 new_sf = None
1904 self._reload_connection()
tierno9c5c8322018-03-23 15:44:03 +01001905 # correlation = None
1906 # if sfc_encap:
1907 # correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001908 for instance in sfis:
1909 sfi = self.get_sfi(instance)
Igor D.Ccaadc442017-11-06 12:48:48 +00001910 if sfi.get('sfc_encap') != sfc_encap:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001911 raise vimconn.vimconnNotSupportedException(
1912 "OpenStack VIM connector requires all SFIs of the "
1913 "same SF to share the same SFC Encapsulation")
1914 sf_dict = {'name': name,
1915 'port_pairs': sfis}
Igor D.Ccaadc442017-11-06 12:48:48 +00001916 new_sf = self.neutron.create_sfc_port_pair_group({
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001917 'port_pair_group': sf_dict})
1918 return new_sf['port_pair_group']['id']
1919 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1920 neExceptions.NeutronException, ConnectionError) as e:
1921 if new_sf:
1922 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00001923 self.neutron.delete_sfc_port_pair_group(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001924 new_sf['port_pair_group']['id'])
1925 except Exception:
1926 self.logger.error(
1927 'Creation of Service Function failed, with '
1928 'subsequent deletion failure as well.')
1929 self._format_exception(e)
1930
1931 def get_sf(self, sf_id):
1932 self.logger.debug("Getting Service Function %s from VIM", sf_id)
1933 filter_dict = {"id": sf_id}
1934 sf_list = self.get_sf_list(filter_dict)
1935 if len(sf_list) == 0:
1936 raise vimconn.vimconnNotFoundException(
1937 "Service Function '{}' not found".format(sf_id))
1938 elif len(sf_list) > 1:
1939 raise vimconn.vimconnConflictException(
1940 "Found more than one Service Function with this criteria")
1941 sf = sf_list[0]
1942 return sf
1943
1944 def get_sf_list(self, filter_dict={}):
1945 self.logger.debug("Getting Service Function from VIM filter: '%s'",
1946 str(filter_dict))
1947 try:
1948 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001949 filter_dict_os = filter_dict.copy()
1950 if self.api_version3 and "tenant_id" in filter_dict_os:
1951 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
1952 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001953 sf_list = sf_dict["port_pair_groups"]
1954 self.__sf_os2mano(sf_list)
1955 return sf_list
1956 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1957 neExceptions.NeutronException, ConnectionError) as e:
1958 self._format_exception(e)
1959
1960 def delete_sf(self, sf_id):
1961 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
1962 try:
1963 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001964 self.neutron.delete_sfc_port_pair_group(sf_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001965 return sf_id
1966 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1967 ksExceptions.ClientException, neExceptions.NeutronException,
1968 ConnectionError) as e:
1969 self._format_exception(e)
1970
1971 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
1972 self.logger.debug("Adding a new Service Function Path to VIM, "
1973 "named '%s'", name)
1974 try:
1975 new_sfp = None
1976 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001977 # In networking-sfc the MPLS encapsulation is legacy
1978 # should be used when no full SFC Encapsulation is intended
schillinge981df9a2019-01-24 09:25:11 +01001979 correlation = 'mpls'
Igor D.Ccaadc442017-11-06 12:48:48 +00001980 if sfc_encap:
1981 correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001982 sfp_dict = {'name': name,
1983 'flow_classifiers': classifications,
1984 'port_pair_groups': sfs,
1985 'chain_parameters': {'correlation': correlation}}
1986 if spi:
1987 sfp_dict['chain_id'] = spi
Igor D.Ccaadc442017-11-06 12:48:48 +00001988 new_sfp = self.neutron.create_sfc_port_chain({'port_chain': sfp_dict})
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001989 return new_sfp["port_chain"]["id"]
1990 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1991 neExceptions.NeutronException, ConnectionError) as e:
1992 if new_sfp:
1993 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00001994 self.neutron.delete_sfc_port_chain(new_sfp['port_chain']['id'])
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001995 except Exception:
1996 self.logger.error(
1997 'Creation of Service Function Path failed, with '
1998 'subsequent deletion failure as well.')
1999 self._format_exception(e)
2000
2001 def get_sfp(self, sfp_id):
2002 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
2003 filter_dict = {"id": sfp_id}
2004 sfp_list = self.get_sfp_list(filter_dict)
2005 if len(sfp_list) == 0:
2006 raise vimconn.vimconnNotFoundException(
2007 "Service Function Path '{}' not found".format(sfp_id))
2008 elif len(sfp_list) > 1:
2009 raise vimconn.vimconnConflictException(
2010 "Found more than one Service Function Path with this criteria")
2011 sfp = sfp_list[0]
2012 return sfp
2013
2014 def get_sfp_list(self, filter_dict={}):
2015 self.logger.debug("Getting Service Function Paths from VIM filter: "
2016 "'%s'", str(filter_dict))
2017 try:
2018 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01002019 filter_dict_os = filter_dict.copy()
2020 if self.api_version3 and "tenant_id" in filter_dict_os:
2021 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
2022 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002023 sfp_list = sfp_dict["port_chains"]
2024 self.__sfp_os2mano(sfp_list)
2025 return sfp_list
2026 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2027 neExceptions.NeutronException, ConnectionError) as e:
2028 self._format_exception(e)
2029
2030 def delete_sfp(self, sfp_id):
2031 self.logger.debug(
2032 "Deleting Service Function Path '%s' from VIM", sfp_id)
2033 try:
2034 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00002035 self.neutron.delete_sfc_port_chain(sfp_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002036 return sfp_id
2037 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2038 ksExceptions.ClientException, neExceptions.NeutronException,
2039 ConnectionError) as e:
2040 self._format_exception(e)