blob: 2167fd40a3cf1bd0bbccacb0c99ebfa7c5dd881c [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)
fatollahy40c6a3f2019-02-19 12:53:40 +0000230 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River Titanium cloud and StarlingX
231 region_name = self.config.get('region_name')
tiernof716aea2017-06-21 18:01:40 +0200232 if self.api_version3:
fatollahy40c6a3f2019-02-19 12:53:40 +0000233 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
tiernof716aea2017-06-21 18:01:40 +0200234 else:
kate721d79b2017-06-24 04:21:38 -0700235 self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200236 self.session['keystone'] = self.keystone
montesmoreno9317d302017-08-16 12:48:23 +0200237 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
238 # This implementation approach is due to the warning message in
239 # https://developer.openstack.org/api-guide/compute/microversions.html
240 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
241 # always require an specific microversion.
242 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
243 version = self.config.get("microversion")
244 if not version:
245 version = "2.1"
fatollahy40c6a3f2019-02-19 12:53:40 +0000246 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River Titanium cloud and StarlingX
247 self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
248 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
249 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
kate721d79b2017-06-24 04:21:38 -0700250 if self.endpoint_type == "internalURL":
251 glance_service_id = self.keystone.services.list(name="glance")[0].id
252 glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
253 else:
254 glance_endpoint = None
255 self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
256 #using version 1 of glance client in new_image()
tierno1beea862018-07-11 15:47:37 +0200257 # self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
258 # endpoint=glance_endpoint)
tiernob5cef372017-06-19 15:52:22 +0200259 self.session['reload_client'] = False
260 self.persistent_info['session'] = self.session
mirabal29356312017-07-27 12:21:22 +0200261 # add availablity zone info inside self.persistent_info
262 self._set_availablity_zones()
263 self.persistent_info['availability_zone'] = self.availability_zone
ahmadsa95baa272016-11-30 09:14:11 +0500264
tierno7edb6752016-03-21 17:37:52 +0100265 def __net_os2mano(self, net_list_dict):
266 '''Transform the net openstack format to mano format
267 net_list_dict can be a list of dict or a single dict'''
268 if type(net_list_dict) is dict:
269 net_list_=(net_list_dict,)
270 elif type(net_list_dict) is list:
271 net_list_=net_list_dict
272 else:
273 raise TypeError("param net_list_dict must be a list or a dictionary")
274 for net in net_list_:
275 if net.get('provider:network_type') == "vlan":
276 net['type']='data'
277 else:
278 net['type']='bridge'
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200279
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000280 def __classification_os2mano(self, class_list_dict):
281 """Transform the openstack format (Flow Classifier) to mano format
282 (Classification) class_list_dict can be a list of dict or a single dict
283 """
284 if isinstance(class_list_dict, dict):
285 class_list_ = [class_list_dict]
286 elif isinstance(class_list_dict, list):
287 class_list_ = class_list_dict
288 else:
289 raise TypeError(
290 "param class_list_dict must be a list or a dictionary")
291 for classification in class_list_:
292 id = classification.pop('id')
293 name = classification.pop('name')
294 description = classification.pop('description')
295 project_id = classification.pop('project_id')
296 tenant_id = classification.pop('tenant_id')
297 original_classification = copy.deepcopy(classification)
298 classification.clear()
299 classification['ctype'] = 'legacy_flow_classifier'
300 classification['definition'] = original_classification
301 classification['id'] = id
302 classification['name'] = name
303 classification['description'] = description
304 classification['project_id'] = project_id
305 classification['tenant_id'] = tenant_id
306
307 def __sfi_os2mano(self, sfi_list_dict):
308 """Transform the openstack format (Port Pair) to mano format (SFI)
309 sfi_list_dict can be a list of dict or a single dict
310 """
311 if isinstance(sfi_list_dict, dict):
312 sfi_list_ = [sfi_list_dict]
313 elif isinstance(sfi_list_dict, list):
314 sfi_list_ = sfi_list_dict
315 else:
316 raise TypeError(
317 "param sfi_list_dict must be a list or a dictionary")
318 for sfi in sfi_list_:
319 sfi['ingress_ports'] = []
320 sfi['egress_ports'] = []
321 if sfi.get('ingress'):
322 sfi['ingress_ports'].append(sfi['ingress'])
323 if sfi.get('egress'):
324 sfi['egress_ports'].append(sfi['egress'])
325 del sfi['ingress']
326 del sfi['egress']
327 params = sfi.get('service_function_parameters')
328 sfc_encap = False
329 if params:
330 correlation = params.get('correlation')
331 if correlation:
332 sfc_encap = True
333 sfi['sfc_encap'] = sfc_encap
334 del sfi['service_function_parameters']
335
336 def __sf_os2mano(self, sf_list_dict):
337 """Transform the openstack format (Port Pair Group) to mano format (SF)
338 sf_list_dict can be a list of dict or a single dict
339 """
340 if isinstance(sf_list_dict, dict):
341 sf_list_ = [sf_list_dict]
342 elif isinstance(sf_list_dict, list):
343 sf_list_ = sf_list_dict
344 else:
345 raise TypeError(
346 "param sf_list_dict must be a list or a dictionary")
347 for sf in sf_list_:
348 del sf['port_pair_group_parameters']
349 sf['sfis'] = sf['port_pairs']
350 del sf['port_pairs']
351
352 def __sfp_os2mano(self, sfp_list_dict):
353 """Transform the openstack format (Port Chain) to mano format (SFP)
354 sfp_list_dict can be a list of dict or a single dict
355 """
356 if isinstance(sfp_list_dict, dict):
357 sfp_list_ = [sfp_list_dict]
358 elif isinstance(sfp_list_dict, list):
359 sfp_list_ = sfp_list_dict
360 else:
361 raise TypeError(
362 "param sfp_list_dict must be a list or a dictionary")
363 for sfp in sfp_list_:
364 params = sfp.pop('chain_parameters')
365 sfc_encap = False
366 if params:
367 correlation = params.get('correlation')
368 if correlation:
369 sfc_encap = True
370 sfp['sfc_encap'] = sfc_encap
371 sfp['spi'] = sfp.pop('chain_id')
372 sfp['classifications'] = sfp.pop('flow_classifiers')
373 sfp['service_functions'] = sfp.pop('port_pair_groups')
374
375 # placeholder for now; read TODO note below
376 def _validate_classification(self, type, definition):
377 # only legacy_flow_classifier Type is supported at this point
378 return True
379 # TODO(igordcard): this method should be an abstract method of an
380 # abstract Classification class to be implemented by the specific
381 # Types. Also, abstract vimconnector should call the validation
382 # method before the implemented VIM connectors are called.
383
tiernoae4a8d12016-07-08 12:30:39 +0200384 def _format_exception(self, exception):
385 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
shashankjain3c83a212018-10-04 13:05:46 +0530386 if isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound, ksExceptions.NotFound, gl1Exceptions.HTTPNotFound)):
tiernoae4a8d12016-07-08 12:30:39 +0200387 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
shashankjain3c83a212018-10-04 13:05:46 +0530388 elif isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
389 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed)):
390 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
391 elif isinstance(exception, (KeyError, nvExceptions.BadRequest, ksExceptions.BadRequest)):
anwarsc76a3ee2018-10-04 14:05:32 +0530392 raise vimconn.vimconnException(type(exception).__name__ + ": " + str(exception))
393 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
394 neExceptions.NeutronException)):
395 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
tiernoae4a8d12016-07-08 12:30:39 +0200396 elif isinstance(exception, nvExceptions.Conflict):
397 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200398 elif isinstance(exception, vimconn.vimconnException):
tierno41a69812018-02-16 14:34:33 +0100399 raise exception
tiernof716aea2017-06-21 18:01:40 +0200400 else: # ()
tiernob84cbdc2017-07-07 14:30:30 +0200401 self.logger.error("General Exception " + str(exception), exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +0200402 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
403
404 def get_tenant_list(self, filter_dict={}):
405 '''Obtain tenants of VIM
406 filter_dict can contain the following keys:
407 name: filter by tenant name
408 id: filter by tenant uuid/id
409 <other VIM specific>
410 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
411 '''
ahmadsa95baa272016-11-30 09:14:11 +0500412 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200413 try:
414 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200415 if self.api_version3:
416 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500417 else:
tiernof716aea2017-06-21 18:01:40 +0200418 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500419 project_list=[]
420 for project in project_class_list:
tiernof716aea2017-06-21 18:01:40 +0200421 if filter_dict.get('id') and filter_dict["id"] != project.id:
422 continue
ahmadsa95baa272016-11-30 09:14:11 +0500423 project_list.append(project.to_dict())
424 return project_list
tiernof716aea2017-06-21 18:01:40 +0200425 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200426 self._format_exception(e)
427
428 def new_tenant(self, tenant_name, tenant_description):
429 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
430 self.logger.debug("Adding a new tenant name: %s", tenant_name)
431 try:
432 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200433 if self.api_version3:
434 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
435 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500436 else:
tiernof716aea2017-06-21 18:01:40 +0200437 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500438 return project.id
shashankjain3c83a212018-10-04 13:05:46 +0530439 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200440 self._format_exception(e)
441
442 def delete_tenant(self, tenant_id):
443 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
444 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
445 try:
446 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200447 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500448 self.keystone.projects.delete(tenant_id)
449 else:
450 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200451 return tenant_id
shashankjain3c83a212018-10-04 13:05:46 +0530452 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200453 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500454
garciadeblas9f8456e2016-09-05 05:02:59 +0200455 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200456 '''Adds a tenant network to VIM. Returns the network identifier'''
457 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000458 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100459 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000460 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100461 self._reload_connection()
462 network_dict = {'name': net_name, 'admin_state_up': True}
463 if net_type=="data" or net_type=="ptp":
464 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200465 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100466 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
467 network_dict["provider:network_type"] = "vlan"
468 if vlan!=None:
469 network_dict["provider:network_type"] = vlan
kate721d79b2017-06-24 04:21:38 -0700470
471 ####### VIO Specific Changes #########
472 if self.vim_type == "VIO":
473 if vlan is not None:
474 network_dict["provider:segmentation_id"] = vlan
475 else:
476 if self.config.get('dataplane_net_vlan_range') is None:
477 raise vimconn.vimconnConflictException("You must provide "\
478 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
479 "at config value before creating sriov network with vlan tag")
480
481 network_dict["provider:segmentation_id"] = self._genrate_vlanID()
482
tiernoae4a8d12016-07-08 12:30:39 +0200483 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100484 new_net=self.neutron.create_network({'network':network_dict})
485 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200486 #create subnetwork, even if there is no profile
487 if not ip_profile:
488 ip_profile = {}
tierno41a69812018-02-16 14:34:33 +0100489 if not ip_profile.get('subnet_address'):
garciadeblas2299e3b2017-01-26 14:35:55 +0000490 #Fake subnet is required
491 subnet_rand = random.randint(0, 255)
492 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000493 if 'ip_version' not in ip_profile:
garciadeblas9f8456e2016-09-05 05:02:59 +0200494 ip_profile['ip_version'] = "IPv4"
tiernoa1fb4462017-06-30 12:25:50 +0200495 subnet = {"name":net_name+"-subnet",
tierno7edb6752016-03-21 17:37:52 +0100496 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200497 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
498 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100499 }
tiernoa1fb4462017-06-30 12:25:50 +0200500 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
tierno41a69812018-02-16 14:34:33 +0100501 if ip_profile.get('gateway_address'):
tierno55d234c2018-07-04 18:29:21 +0200502 subnet['gateway_ip'] = ip_profile['gateway_address']
503 else:
504 subnet['gateway_ip'] = None
garciadeblasedca7b32016-09-29 14:01:52 +0000505 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200506 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200507 if 'dhcp_enabled' in ip_profile:
tierno41a69812018-02-16 14:34:33 +0100508 subnet['enable_dhcp'] = False if \
509 ip_profile['dhcp_enabled']=="false" or ip_profile['dhcp_enabled']==False else True
510 if ip_profile.get('dhcp_start_address'):
tiernoa1fb4462017-06-30 12:25:50 +0200511 subnet['allocation_pools'] = []
garciadeblas9f8456e2016-09-05 05:02:59 +0200512 subnet['allocation_pools'].append(dict())
513 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
tierno41a69812018-02-16 14:34:33 +0100514 if ip_profile.get('dhcp_count'):
garciadeblas9f8456e2016-09-05 05:02:59 +0200515 #parts = ip_profile['dhcp_start_address'].split('.')
516 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
517 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200518 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200519 ip_str = str(netaddr.IPAddress(ip_int))
520 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000521 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100522 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200523 return new_net["network"]["id"]
tierno41a69812018-02-16 14:34:33 +0100524 except Exception as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000525 if new_net:
526 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200527 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100528
529 def get_network_list(self, filter_dict={}):
530 '''Obtain tenant networks of VIM
531 Filter_dict can be:
532 name: network name
533 id: network uuid
534 shared: boolean
535 tenant_id: tenant
536 admin_state_up: boolean
537 status: 'ACTIVE'
538 Returns the network list of dictionaries
539 '''
tiernoae4a8d12016-07-08 12:30:39 +0200540 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100541 try:
542 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +0100543 filter_dict_os = filter_dict.copy()
544 if self.api_version3 and "tenant_id" in filter_dict_os:
545 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id') #T ODO check
546 net_dict = self.neutron.list_networks(**filter_dict_os)
tierno00e3df72017-11-29 17:20:13 +0100547 net_list = net_dict["networks"]
tierno7edb6752016-03-21 17:37:52 +0100548 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200549 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000550 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200551 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100552
tiernoae4a8d12016-07-08 12:30:39 +0200553 def get_network(self, net_id):
554 '''Obtain details of network from VIM
555 Returns the network information from a network id'''
556 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100557 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200558 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100559 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200560 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100561 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200562 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100563 net = net_list[0]
564 subnets=[]
565 for subnet_id in net.get("subnets", () ):
566 try:
567 subnet = self.neutron.show_subnet(subnet_id)
568 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200569 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
570 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100571 subnets.append(subnet)
572 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100573 net["encapsulation"] = net.get('provider:network_type')
Anderson Bravalheri0fb70282018-12-16 19:28:37 +0000574 net["encapsulation_type"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100575 net["segmentation_id"] = net.get('provider:segmentation_id')
Anderson Bravalheri0fb70282018-12-16 19:28:37 +0000576 net["encapsulation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200577 return net
tierno7edb6752016-03-21 17:37:52 +0100578
tiernoae4a8d12016-07-08 12:30:39 +0200579 def delete_network(self, net_id):
580 '''Deletes a tenant network from VIM. Returns the old network identifier'''
581 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100582 try:
583 self._reload_connection()
584 #delete VM ports attached to this networks before the network
585 ports = self.neutron.list_ports(network_id=net_id)
586 for p in ports['ports']:
587 try:
588 self.neutron.delete_port(p["id"])
589 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200590 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100591 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200592 return net_id
593 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000594 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200595 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100596
tiernoae4a8d12016-07-08 12:30:39 +0200597 def refresh_nets_status(self, net_list):
598 '''Get the status of the networks
599 Params: the list of network identifiers
600 Returns a dictionary with:
601 net_id: #VIM id of this network
602 status: #Mandatory. Text with one of:
603 # DELETED (not found at vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100604 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
tiernoae4a8d12016-07-08 12:30:39 +0200605 # OTHER (Vim reported other status not understood)
606 # ERROR (VIM indicates an ERROR status)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100607 # ACTIVE, INACTIVE, DOWN (admin down),
tiernoae4a8d12016-07-08 12:30:39 +0200608 # BUILD (on building process)
609 #
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100610 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
tiernoae4a8d12016-07-08 12:30:39 +0200611 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
612
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000613 '''
tiernoae4a8d12016-07-08 12:30:39 +0200614 net_dict={}
615 for net_id in net_list:
616 net = {}
617 try:
618 net_vim = self.get_network(net_id)
619 if net_vim['status'] in netStatus2manoFormat:
620 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
621 else:
622 net["status"] = "OTHER"
623 net["error_msg"] = "VIM status reported " + net_vim['status']
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000624
tierno8e995ce2016-09-22 08:13:00 +0000625 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200626 net['status'] = 'DOWN'
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100627
628 net['vim_info'] = self.serialize(net_vim)
629
tiernoae4a8d12016-07-08 12:30:39 +0200630 if net_vim.get('fault'): #TODO
631 net['error_msg'] = str(net_vim['fault'])
632 except vimconn.vimconnNotFoundException as e:
633 self.logger.error("Exception getting net status: %s", str(e))
634 net['status'] = "DELETED"
635 net['error_msg'] = str(e)
636 except vimconn.vimconnException as e:
637 self.logger.error("Exception getting net status: %s", str(e))
638 net['status'] = "VIM_ERROR"
639 net['error_msg'] = str(e)
640 net_dict[net_id] = net
641 return net_dict
642
643 def get_flavor(self, flavor_id):
644 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
645 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100646 try:
647 self._reload_connection()
648 flavor = self.nova.flavors.find(id=flavor_id)
649 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200650 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000651 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200652 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100653
tiernocf157a82017-01-30 14:07:06 +0100654 def get_flavor_id_from_data(self, flavor_dict):
655 """Obtain flavor id that match the flavor description
656 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200657 flavor_dict: contains the required ram, vcpus, disk
658 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
659 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
660 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100661 """
tiernoe26fc7a2017-05-30 14:43:03 +0200662 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100663 try:
664 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200665 flavor_candidate_id = None
666 flavor_candidate_data = (10000, 10000, 10000)
667 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
668 # numa=None
669 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100670 if numas:
671 #TODO
672 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
673 # if len(numas) > 1:
674 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
675 # numa=numas[0]
676 # numas = extended.get("numas")
677 for flavor in self.nova.flavors.list():
678 epa = flavor.get_keys()
679 if epa:
680 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200681 # TODO
682 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
683 if flavor_data == flavor_target:
684 return flavor.id
685 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
686 flavor_candidate_id = flavor.id
687 flavor_candidate_data = flavor_data
688 if not exact_match and flavor_candidate_id:
689 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100690 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
691 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
692 self._format_exception(e)
693
tiernoae4a8d12016-07-08 12:30:39 +0200694 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100695 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200696 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 +0100697 Returns the flavor identifier
698 '''
tiernoae4a8d12016-07-08 12:30:39 +0200699 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100700 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200701 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100702 name_suffix = 0
anwarsc76a3ee2018-10-04 14:05:32 +0530703 try:
704 name=flavor_data['name']
705 while retry<max_retries:
706 retry+=1
707 try:
708 self._reload_connection()
709 if change_name_if_used:
710 #get used names
711 fl_names=[]
712 fl=self.nova.flavors.list()
713 for f in fl:
714 fl_names.append(f.name)
715 while name in fl_names:
716 name_suffix += 1
717 name = flavor_data['name']+"-" + str(name_suffix)
kate721d79b2017-06-24 04:21:38 -0700718
anwarsc76a3ee2018-10-04 14:05:32 +0530719 ram = flavor_data.get('ram',64)
720 vcpus = flavor_data.get('vcpus',1)
721 numa_properties=None
tierno7edb6752016-03-21 17:37:52 +0100722
anwarsc76a3ee2018-10-04 14:05:32 +0530723 extended = flavor_data.get("extended")
724 if extended:
725 numas=extended.get("numas")
726 if numas:
727 numa_nodes = len(numas)
728 if numa_nodes > 1:
729 return -1, "Can not add flavor with more than one numa"
730 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
731 numa_properties["hw:mem_page_size"] = "large"
732 numa_properties["hw:cpu_policy"] = "dedicated"
733 numa_properties["hw:numa_mempolicy"] = "strict"
734 if self.vim_type == "VIO":
735 numa_properties["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
736 numa_properties["vmware:latency_sensitivity_level"] = "high"
737 for numa in numas:
738 #overwrite ram and vcpus
739 #check if key 'memory' is present in numa else use ram value at flavor
740 if 'memory' in numa:
741 ram = numa['memory']*1024
742 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
743 if 'paired-threads' in numa:
744 vcpus = numa['paired-threads']*2
745 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
746 numa_properties["hw:cpu_thread_policy"] = "require"
747 numa_properties["hw:cpu_policy"] = "dedicated"
748 elif 'cores' in numa:
749 vcpus = numa['cores']
750 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
751 numa_properties["hw:cpu_thread_policy"] = "isolate"
752 numa_properties["hw:cpu_policy"] = "dedicated"
753 elif 'threads' in numa:
754 vcpus = numa['threads']
755 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
756 numa_properties["hw:cpu_thread_policy"] = "prefer"
757 numa_properties["hw:cpu_policy"] = "dedicated"
758 # for interface in numa.get("interfaces",() ):
759 # if interface["dedicated"]=="yes":
760 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
761 # #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 +0000762
anwarsc76a3ee2018-10-04 14:05:32 +0530763 #create flavor
764 new_flavor=self.nova.flavors.create(name,
765 ram,
766 vcpus,
767 flavor_data.get('disk',0),
768 is_public=flavor_data.get('is_public', True)
769 )
770 #add metadata
771 if numa_properties:
772 new_flavor.set_keys(numa_properties)
773 return new_flavor.id
774 except nvExceptions.Conflict as e:
775 if change_name_if_used and retry < max_retries:
776 continue
777 self._format_exception(e)
778 #except nvExceptions.BadRequest as e:
779 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError, KeyError) as e:
780 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100781
tiernoae4a8d12016-07-08 12:30:39 +0200782 def delete_flavor(self,flavor_id):
783 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100784 '''
tiernoae4a8d12016-07-08 12:30:39 +0200785 try:
786 self._reload_connection()
787 self.nova.flavors.delete(flavor_id)
788 return flavor_id
789 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000790 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200791 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100792
tiernoae4a8d12016-07-08 12:30:39 +0200793 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100794 '''
tiernoae4a8d12016-07-08 12:30:39 +0200795 Adds a tenant image to VIM. imge_dict is a dictionary with:
796 name: name
797 disk_format: qcow2, vhd, vmdk, raw (by default), ...
798 location: path or URI
799 public: "yes" or "no"
800 metadata: metadata of the image
801 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100802 '''
tiernoae4a8d12016-07-08 12:30:39 +0200803 retry=0
804 max_retries=3
805 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100806 retry+=1
807 try:
808 self._reload_connection()
809 #determine format http://docs.openstack.org/developer/glance/formats.html
810 if "disk_format" in image_dict:
811 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100812 else: #autodiscover based on extension
tierno1beea862018-07-11 15:47:37 +0200813 if image_dict['location'].endswith(".qcow2"):
tierno7edb6752016-03-21 17:37:52 +0100814 disk_format="qcow2"
tierno1beea862018-07-11 15:47:37 +0200815 elif image_dict['location'].endswith(".vhd"):
tierno7edb6752016-03-21 17:37:52 +0100816 disk_format="vhd"
tierno1beea862018-07-11 15:47:37 +0200817 elif image_dict['location'].endswith(".vmdk"):
tierno7edb6752016-03-21 17:37:52 +0100818 disk_format="vmdk"
tierno1beea862018-07-11 15:47:37 +0200819 elif image_dict['location'].endswith(".vdi"):
tierno7edb6752016-03-21 17:37:52 +0100820 disk_format="vdi"
tierno1beea862018-07-11 15:47:37 +0200821 elif image_dict['location'].endswith(".iso"):
tierno7edb6752016-03-21 17:37:52 +0100822 disk_format="iso"
tierno1beea862018-07-11 15:47:37 +0200823 elif image_dict['location'].endswith(".aki"):
tierno7edb6752016-03-21 17:37:52 +0100824 disk_format="aki"
tierno1beea862018-07-11 15:47:37 +0200825 elif image_dict['location'].endswith(".ari"):
tierno7edb6752016-03-21 17:37:52 +0100826 disk_format="ari"
tierno1beea862018-07-11 15:47:37 +0200827 elif image_dict['location'].endswith(".ami"):
tierno7edb6752016-03-21 17:37:52 +0100828 disk_format="ami"
829 else:
830 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200831 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
shashankjain3c83a212018-10-04 13:05:46 +0530832 if self.vim_type == "VIO":
833 container_format = "bare"
834 if 'container_format' in image_dict:
835 container_format = image_dict['container_format']
836 new_image = self.glance.images.create(name=image_dict['name'], container_format=container_format,
837 disk_format=disk_format)
838 else:
839 new_image = self.glance.images.create(name=image_dict['name'])
tierno1beea862018-07-11 15:47:37 +0200840 if image_dict['location'].startswith("http"):
841 # TODO there is not a method to direct download. It must be downloaded locally with requests
842 raise vimconn.vimconnNotImplemented("Cannot create image from URL")
tierno7edb6752016-03-21 17:37:52 +0100843 else: #local path
844 with open(image_dict['location']) as fimage:
tierno1beea862018-07-11 15:47:37 +0200845 self.glance.images.upload(new_image.id, fimage)
846 #new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
847 # container_format="bare", data=fimage, disk_format=disk_format)
tierno7edb6752016-03-21 17:37:52 +0100848 metadata_to_load = image_dict.get('metadata')
shashankjain3c83a212018-10-04 13:05:46 +0530849 # TODO location is a reserved word for current openstack versions. fixed for VIO please check for openstack
850 if self.vim_type == "VIO":
851 metadata_to_load['upload_location'] = image_dict['location']
852 else:
853 metadata_to_load['location'] = image_dict['location']
tierno1beea862018-07-11 15:47:37 +0200854 self.glance.images.update(new_image.id, **metadata_to_load)
tiernoae4a8d12016-07-08 12:30:39 +0200855 return new_image.id
856 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
857 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000858 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200859 if retry==max_retries:
860 continue
861 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100862 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200863 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
864 http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000865
tiernoae4a8d12016-07-08 12:30:39 +0200866 def delete_image(self, image_id):
867 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100868 '''
tiernoae4a8d12016-07-08 12:30:39 +0200869 try:
870 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +0200871 self.glance.images.delete(image_id)
tiernoae4a8d12016-07-08 12:30:39 +0200872 return image_id
shashankjain3c83a212018-10-04 13:05:46 +0530873 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, gl1Exceptions.HTTPNotFound, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200874 self._format_exception(e)
875
876 def get_image_id_from_path(self, path):
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000877 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200878 try:
879 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +0200880 images = self.glance.images.list()
tiernoae4a8d12016-07-08 12:30:39 +0200881 for image in images:
882 if image.metadata.get("location")==path:
883 return image.id
884 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000885 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200886 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000887
garciadeblasb69fa9f2016-09-28 12:04:10 +0200888 def get_image_list(self, filter_dict={}):
889 '''Obtain tenant images from VIM
890 Filter_dict can be:
891 id: image id
892 name: image name
893 checksum: image checksum
894 Returns the image list of dictionaries:
895 [{<the fields at Filter_dict plus some VIM specific>}, ...]
896 List can be empty
897 '''
898 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
899 try:
900 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +0100901 filter_dict_os = filter_dict.copy()
garciadeblasb69fa9f2016-09-28 12:04:10 +0200902 #First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +0200903 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +0200904 filtered_list = []
905 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +0200906 try:
tierno1beea862018-07-11 15:47:37 +0200907 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
908 continue
909 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
910 continue
911 if filter_dict.get("checksum") and image["checksum"] != filter_dict["checksum"]:
912 continue
913
914 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +0200915 except gl1Exceptions.HTTPNotFound:
916 pass
garciadeblasb69fa9f2016-09-28 12:04:10 +0200917 return filtered_list
918 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
919 self._format_exception(e)
920
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200921 def __wait_for_vm(self, vm_id, status):
922 """wait until vm is in the desired status and return True.
923 If the VM gets in ERROR status, return false.
924 If the timeout is reached generate an exception"""
925 elapsed_time = 0
926 while elapsed_time < server_timeout:
927 vm_status = self.nova.servers.get(vm_id).status
928 if vm_status == status:
929 return True
930 if vm_status == 'ERROR':
931 return False
tierno1df468d2018-07-06 14:25:16 +0200932 time.sleep(5)
933 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200934
935 # if we exceeded the timeout rollback
936 if elapsed_time >= server_timeout:
937 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
938 http_code=vimconn.HTTP_Request_Timeout)
939
mirabal29356312017-07-27 12:21:22 +0200940 def _get_openstack_availablity_zones(self):
941 """
942 Get from openstack availability zones available
943 :return:
944 """
945 try:
946 openstack_availability_zone = self.nova.availability_zones.list()
947 openstack_availability_zone = [str(zone.zoneName) for zone in openstack_availability_zone
948 if zone.zoneName != 'internal']
949 return openstack_availability_zone
950 except Exception as e:
951 return None
952
953 def _set_availablity_zones(self):
954 """
955 Set vim availablity zone
956 :return:
957 """
958
959 if 'availability_zone' in self.config:
960 vim_availability_zones = self.config.get('availability_zone')
961 if isinstance(vim_availability_zones, str):
962 self.availability_zone = [vim_availability_zones]
963 elif isinstance(vim_availability_zones, list):
964 self.availability_zone = vim_availability_zones
965 else:
966 self.availability_zone = self._get_openstack_availablity_zones()
967
tierno5a3273c2017-08-29 11:43:46 +0200968 def _get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
mirabal29356312017-07-27 12:21:22 +0200969 """
tierno5a3273c2017-08-29 11:43:46 +0200970 Return thge availability zone to be used by the created VM.
971 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +0200972 """
tierno5a3273c2017-08-29 11:43:46 +0200973 if availability_zone_index is None:
974 if not self.config.get('availability_zone'):
975 return None
976 elif isinstance(self.config.get('availability_zone'), str):
977 return self.config['availability_zone']
978 else:
979 # TODO consider using a different parameter at config for default AV and AV list match
980 return self.config['availability_zone'][0]
mirabal29356312017-07-27 12:21:22 +0200981
tierno5a3273c2017-08-29 11:43:46 +0200982 vim_availability_zones = self.availability_zone
983 # check if VIM offer enough availability zones describe in the VNFD
984 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
985 # check if all the names of NFV AV match VIM AV names
986 match_by_index = False
987 for av in availability_zone_list:
988 if av not in vim_availability_zones:
989 match_by_index = True
990 break
991 if match_by_index:
992 return vim_availability_zones[availability_zone_index]
993 else:
994 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +0200995 else:
tierno5a3273c2017-08-29 11:43:46 +0200996 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
mirabal29356312017-07-27 12:21:22 +0200997
tierno5a3273c2017-08-29 11:43:46 +0200998 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
999 availability_zone_index=None, availability_zone_list=None):
tierno98e909c2017-10-14 13:27:03 +02001000 """Adds a VM instance to VIM
tierno7edb6752016-03-21 17:37:52 +01001001 Params:
1002 start: indicates if VM must start or boot in pause mode. Ignored
1003 image_id,flavor_id: iamge and flavor uuid
1004 net_list: list of interfaces, each one is a dictionary with:
1005 name:
1006 net_id: network uuid to connect
1007 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
1008 model: interface model, ignored #TODO
1009 mac_address: used for SR-IOV ifaces #TODO for other types
1010 use: 'data', 'bridge', 'mgmt'
tierno66eba6e2017-11-10 17:09:18 +01001011 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
tierno7edb6752016-03-21 17:37:52 +01001012 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +05001013 floating_ip: True/False (or it can be None)
tierno41a69812018-02-16 14:34:33 +01001014 'cloud_config': (optional) dictionary with:
1015 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
1016 'users': (optional) list of users to be inserted, each item is a dict with:
1017 'name': (mandatory) user name,
1018 'key-pairs': (optional) list of strings with the public key to be inserted to the user
1019 'user-data': (optional) string is a text script to be passed directly to cloud-init
1020 'config-files': (optional). List of files to be transferred. Each item is a dict with:
1021 'dest': (mandatory) string with the destination absolute path
1022 'encoding': (optional, by default text). Can be one of:
1023 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
1024 'content' (mandatory): string with the content of the file
1025 'permissions': (optional) string with file permissions, typically octal notation '0644'
1026 'owner': (optional) file owner, string with the format 'owner:group'
1027 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
mirabal29356312017-07-27 12:21:22 +02001028 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
1029 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
1030 'size': (mandatory) string with the size of the disk in GB
tierno1df468d2018-07-06 14:25:16 +02001031 'vim_id' (optional) should use this existing volume id
tierno5a3273c2017-08-29 11:43:46 +02001032 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
1033 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
1034 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01001035 #TODO ip, security groups
tierno98e909c2017-10-14 13:27:03 +02001036 Returns a tuple with the instance identifier and created_items or raises an exception on error
1037 created_items can be None or a dictionary where this method can include key-values that will be passed to
1038 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
1039 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
1040 as not present.
1041 """
tiernofa51c202017-01-27 14:58:17 +01001042 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 +01001043 try:
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001044 server = None
tierno98e909c2017-10-14 13:27:03 +02001045 created_items = {}
tiernob0b9dab2017-10-14 14:25:20 +02001046 # metadata = {}
tierno98e909c2017-10-14 13:27:03 +02001047 net_list_vim = []
1048 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 +02001049 no_secured_ports = [] # List of port-is with port-security disabled
tierno7edb6752016-03-21 17:37:52 +01001050 self._reload_connection()
tiernob0b9dab2017-10-14 14:25:20 +02001051 # metadata_vpci = {} # For a specific neutron plugin
tiernob84cbdc2017-07-07 14:30:30 +02001052 block_device_mapping = None
tierno7edb6752016-03-21 17:37:52 +01001053 for net in net_list:
tierno98e909c2017-10-14 13:27:03 +02001054 if not net.get("net_id"): # skip non connected iface
tierno7edb6752016-03-21 17:37:52 +01001055 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001056
1057 port_dict={
1058 "network_id": net["net_id"],
1059 "name": net.get("name"),
1060 "admin_state_up": True
1061 }
1062 if net["type"]=="virtual":
tiernob0b9dab2017-10-14 14:25:20 +02001063 pass
1064 # if "vpci" in net:
1065 # metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
tierno66eba6e2017-11-10 17:09:18 +01001066 elif net["type"] == "VF" or net["type"] == "SR-IOV": # for VF
tiernob0b9dab2017-10-14 14:25:20 +02001067 # if "vpci" in net:
1068 # if "VF" not in metadata_vpci:
1069 # metadata_vpci["VF"]=[]
1070 # metadata_vpci["VF"].append([ net["vpci"], "" ])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001071 port_dict["binding:vnic_type"]="direct"
tiernob0b9dab2017-10-14 14:25:20 +02001072 # VIO specific Changes
kate721d79b2017-06-24 04:21:38 -07001073 if self.vim_type == "VIO":
tiernob0b9dab2017-10-14 14:25:20 +02001074 # Need to create port with port_security_enabled = False and no-security-groups
kate721d79b2017-06-24 04:21:38 -07001075 port_dict["port_security_enabled"]=False
1076 port_dict["provider_security_groups"]=[]
1077 port_dict["security_groups"]=[]
tierno66eba6e2017-11-10 17:09:18 +01001078 else: # For PT PCI-PASSTHROUGH
tiernob0b9dab2017-10-14 14:25:20 +02001079 # VIO specific Changes
1080 # Current VIO release does not support port with type 'direct-physical'
1081 # So no need to create virtual port in case of PCI-device.
1082 # Will update port_dict code when support gets added in next VIO release
kate721d79b2017-06-24 04:21:38 -07001083 if self.vim_type == "VIO":
tiernob0b9dab2017-10-14 14:25:20 +02001084 raise vimconn.vimconnNotSupportedException(
1085 "Current VIO release does not support full passthrough (PT)")
1086 # if "vpci" in net:
1087 # if "PF" not in metadata_vpci:
1088 # metadata_vpci["PF"]=[]
1089 # metadata_vpci["PF"].append([ net["vpci"], "" ])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001090 port_dict["binding:vnic_type"]="direct-physical"
1091 if not port_dict["name"]:
1092 port_dict["name"]=name
1093 if net.get("mac_address"):
1094 port_dict["mac_address"]=net["mac_address"]
tierno41a69812018-02-16 14:34:33 +01001095 if net.get("ip_address"):
1096 port_dict["fixed_ips"] = [{'ip_address': net["ip_address"]}]
1097 # TODO add 'subnet_id': <subnet_id>
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001098 new_port = self.neutron.create_port({"port": port_dict })
tierno00e3df72017-11-29 17:20:13 +01001099 created_items["port:" + str(new_port["port"]["id"])] = True
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001100 net["mac_adress"] = new_port["port"]["mac_address"]
1101 net["vim_id"] = new_port["port"]["id"]
tiernob84cbdc2017-07-07 14:30:30 +02001102 # if try to use a network without subnetwork, it will return a emtpy list
1103 fixed_ips = new_port["port"].get("fixed_ips")
1104 if fixed_ips:
1105 net["ip"] = fixed_ips[0].get("ip_address")
1106 else:
1107 net["ip"] = None
montesmoreno994a29d2017-08-22 11:23:06 +02001108
1109 port = {"port-id": new_port["port"]["id"]}
1110 if float(self.nova.api_version.get_string()) >= 2.32:
1111 port["tag"] = new_port["port"]["name"]
1112 net_list_vim.append(port)
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001113
ahmadsaf853d452016-12-22 11:33:47 +05001114 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +01001115 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +05001116 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +01001117 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
1118 net['exit_on_floating_ip_error'] = False
1119 external_network.append(net)
tierno326fd5e2018-02-22 11:58:59 +01001120 net['floating_ip'] = self.config.get('use_floating_ip')
tiernof8383b82017-01-18 15:49:48 +01001121
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001122 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1123 # As a workaround we wait until the VM is active and then disable the port-security
tierno4d1ce222018-04-06 10:41:06 +02001124 if net.get("port_security") == False and not self.config.get("no_port_security_extension"):
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001125 no_secured_ports.append(new_port["port"]["id"])
1126
tiernob0b9dab2017-10-14 14:25:20 +02001127 # if metadata_vpci:
1128 # metadata = {"pci_assignement": json.dumps(metadata_vpci)}
1129 # if len(metadata["pci_assignement"]) >255:
1130 # #limit the metadata size
1131 # #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1132 # self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
1133 # metadata = {}
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001134
tiernob0b9dab2017-10-14 14:25:20 +02001135 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s'",
1136 name, image_id, flavor_id, str(net_list_vim), description)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001137
tiernob0b9dab2017-10-14 14:25:20 +02001138 security_groups = self.config.get('security_groups')
tierno7edb6752016-03-21 17:37:52 +01001139 if type(security_groups) is str:
1140 security_groups = ( security_groups, )
tierno98e909c2017-10-14 13:27:03 +02001141 # cloud config
tierno0a1437e2017-10-02 00:17:43 +02001142 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00001143
tierno98e909c2017-10-14 13:27:03 +02001144 # Create additional volumes in case these are present in disk_list
montesmoreno0c8def02016-12-22 12:16:23 +00001145 base_disk_index = ord('b')
tierno1df468d2018-07-06 14:25:16 +02001146 if disk_list:
tiernob84cbdc2017-07-07 14:30:30 +02001147 block_device_mapping = {}
montesmoreno0c8def02016-12-22 12:16:23 +00001148 for disk in disk_list:
tierno1df468d2018-07-06 14:25:16 +02001149 if disk.get('vim_id'):
1150 block_device_mapping['_vd' + chr(base_disk_index)] = disk['vim_id']
montesmoreno0c8def02016-12-22 12:16:23 +00001151 else:
tierno1df468d2018-07-06 14:25:16 +02001152 if 'image_id' in disk:
1153 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1154 chr(base_disk_index), imageRef=disk['image_id'])
1155 else:
1156 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1157 chr(base_disk_index))
1158 created_items["volume:" + str(volume.id)] = True
1159 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
montesmoreno0c8def02016-12-22 12:16:23 +00001160 base_disk_index += 1
1161
tierno1df468d2018-07-06 14:25:16 +02001162 # Wait until created volumes are with status available
montesmoreno0c8def02016-12-22 12:16:23 +00001163 elapsed_time = 0
tierno1df468d2018-07-06 14:25:16 +02001164 while elapsed_time < volume_timeout:
1165 for created_item in created_items:
1166 v, _, volume_id = created_item.partition(":")
1167 if v == 'volume':
1168 if self.cinder.volumes.get(volume_id).status != 'available':
1169 break
1170 else: # all ready: break from while
1171 break
1172 time.sleep(5)
1173 elapsed_time += 5
tiernob0b9dab2017-10-14 14:25:20 +02001174 # If we exceeded the timeout rollback
montesmoreno0c8def02016-12-22 12:16:23 +00001175 if elapsed_time >= volume_timeout:
montesmoreno0c8def02016-12-22 12:16:23 +00001176 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
1177 http_code=vimconn.HTTP_Request_Timeout)
mirabal29356312017-07-27 12:21:22 +02001178 # get availability Zone
tierno5a3273c2017-08-29 11:43:46 +02001179 vm_av_zone = self._get_vm_availability_zone(availability_zone_index, availability_zone_list)
montesmoreno0c8def02016-12-22 12:16:23 +00001180
tiernob0b9dab2017-10-14 14:25:20 +02001181 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
mirabal29356312017-07-27 12:21:22 +02001182 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
tiernob0b9dab2017-10-14 14:25:20 +02001183 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
mirabal29356312017-07-27 12:21:22 +02001184 security_groups, vm_av_zone, self.config.get('keypair'),
tiernob0b9dab2017-10-14 14:25:20 +02001185 userdata, config_drive, block_device_mapping))
1186 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim,
montesmoreno0c8def02016-12-22 12:16:23 +00001187 security_groups=security_groups,
mirabal29356312017-07-27 12:21:22 +02001188 availability_zone=vm_av_zone,
montesmoreno0c8def02016-12-22 12:16:23 +00001189 key_name=self.config.get('keypair'),
1190 userdata=userdata,
tiernob84cbdc2017-07-07 14:30:30 +02001191 config_drive=config_drive,
1192 block_device_mapping=block_device_mapping
montesmoreno0c8def02016-12-22 12:16:23 +00001193 ) # , description=description)
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001194
tierno326fd5e2018-02-22 11:58:59 +01001195 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001196 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1197 if no_secured_ports:
1198 self.__wait_for_vm(server.id, 'ACTIVE')
1199
1200 for port_id in no_secured_ports:
1201 try:
tierno4d1ce222018-04-06 10:41:06 +02001202 self.neutron.update_port(port_id,
1203 {"port": {"port_security_enabled": False, "security_groups": None}})
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001204 except Exception as e:
tierno4d1ce222018-04-06 10:41:06 +02001205 raise vimconn.vimconnException("It was not possible to disable port security for port {}".format(
1206 port_id))
tierno98e909c2017-10-14 13:27:03 +02001207 # print "DONE :-)", server
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001208
tierno4d1ce222018-04-06 10:41:06 +02001209 # pool_id = None
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001210 if external_network:
tierno98e909c2017-10-14 13:27:03 +02001211 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
ahmadsaf853d452016-12-22 11:33:47 +05001212 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +01001213 try:
tiernof8383b82017-01-18 15:49:48 +01001214 assigned = False
tierno98e909c2017-10-14 13:27:03 +02001215 while not assigned:
tiernof8383b82017-01-18 15:49:48 +01001216 if floating_ips:
1217 ip = floating_ips.pop(0)
tierno326fd5e2018-02-22 11:58:59 +01001218 if ip.get("port_id", False) or ip.get('tenant_id') != server.tenant_id:
1219 continue
1220 if isinstance(floating_network['floating_ip'], str):
1221 if ip.get("floating_network_id") != floating_network['floating_ip']:
1222 continue
1223 free_floating_ip = ip.get("floating_ip_address")
tiernof8383b82017-01-18 15:49:48 +01001224 else:
tiernocb3cca22018-05-31 15:08:52 +02001225 if isinstance(floating_network['floating_ip'], str) and \
1226 floating_network['floating_ip'].lower() != "true":
tierno326fd5e2018-02-22 11:58:59 +01001227 pool_id = floating_network['floating_ip']
1228 else:
tierno4d1ce222018-04-06 10:41:06 +02001229 # Find the external network
tierno326fd5e2018-02-22 11:58:59 +01001230 external_nets = list()
1231 for net in self.neutron.list_networks()['networks']:
1232 if net['router:external']:
1233 external_nets.append(net)
tiernof8383b82017-01-18 15:49:48 +01001234
tierno326fd5e2018-02-22 11:58:59 +01001235 if len(external_nets) == 0:
1236 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
1237 "network is present",
1238 http_code=vimconn.HTTP_Conflict)
1239 if len(external_nets) > 1:
1240 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
1241 "external networks are present",
1242 http_code=vimconn.HTTP_Conflict)
tiernof8383b82017-01-18 15:49:48 +01001243
tierno326fd5e2018-02-22 11:58:59 +01001244 pool_id = external_nets[0].get('id')
tiernof8383b82017-01-18 15:49:48 +01001245 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +05001246 try:
tierno4d1ce222018-04-06 10:41:06 +02001247 # self.logger.debug("Creating floating IP")
tiernof8383b82017-01-18 15:49:48 +01001248 new_floating_ip = self.neutron.create_floatingip(param)
1249 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +05001250 except Exception as e:
tierno326fd5e2018-02-22 11:58:59 +01001251 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create new floating_ip " +
1252 str(e), http_code=vimconn.HTTP_Conflict)
1253
1254 fix_ip = floating_network.get('ip')
1255 while not assigned:
1256 try:
1257 server.add_floating_ip(free_floating_ip, fix_ip)
1258 assigned = True
1259 except Exception as e:
tierno4d1ce222018-04-06 10:41:06 +02001260 # openstack need some time after VM creation to asign an IP. So retry if fails
tierno326fd5e2018-02-22 11:58:59 +01001261 vm_status = self.nova.servers.get(server.id).status
1262 if vm_status != 'ACTIVE' and vm_status != 'ERROR':
1263 if time.time() - vm_start_time < server_timeout:
1264 time.sleep(5)
1265 continue
tierno4d1ce222018-04-06 10:41:06 +02001266 raise vimconn.vimconnException(
1267 "Cannot create floating_ip: {} {}".format(type(e).__name__, e),
1268 http_code=vimconn.HTTP_Conflict)
tierno326fd5e2018-02-22 11:58:59 +01001269
tiernof8383b82017-01-18 15:49:48 +01001270 except Exception as e:
1271 if not floating_network['exit_on_floating_ip_error']:
1272 self.logger.warn("Cannot create floating_ip. %s", str(e))
1273 continue
tiernof8383b82017-01-18 15:49:48 +01001274 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001275
tierno98e909c2017-10-14 13:27:03 +02001276 return server.id, created_items
tierno7edb6752016-03-21 17:37:52 +01001277# except nvExceptions.NotFound as e:
1278# error_value=-vimconn.HTTP_Not_Found
1279# error_text= "vm instance %s not found" % vm_id
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001280# except TypeError as e:
1281# raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1282
1283 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02001284 server_id = None
1285 if server:
1286 server_id = server.id
1287 try:
1288 self.delete_vminstance(server_id, created_items)
1289 except Exception as e2:
1290 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001291
tiernoae4a8d12016-07-08 12:30:39 +02001292 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001293
tiernoae4a8d12016-07-08 12:30:39 +02001294 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +01001295 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001296 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +01001297 try:
1298 self._reload_connection()
1299 server = self.nova.servers.find(id=vm_id)
1300 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +02001301 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +00001302 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001303 self._format_exception(e)
1304
1305 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +01001306 '''
1307 Get a console for the virtual machine
1308 Params:
1309 vm_id: uuid of the VM
1310 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001311 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01001312 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001313 Returns dict with the console parameters:
1314 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001315 server: usually ip address
1316 port: the http, ssh, ... port
1317 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001318 '''
tiernoae4a8d12016-07-08 12:30:39 +02001319 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001320 try:
1321 self._reload_connection()
1322 server = self.nova.servers.find(id=vm_id)
1323 if console_type == None or console_type == "novnc":
1324 console_dict = server.get_vnc_console("novnc")
1325 elif console_type == "xvpvnc":
1326 console_dict = server.get_vnc_console(console_type)
1327 elif console_type == "rdp-html5":
1328 console_dict = server.get_rdp_console(console_type)
1329 elif console_type == "spice-html5":
1330 console_dict = server.get_spice_console(console_type)
1331 else:
tiernoae4a8d12016-07-08 12:30:39 +02001332 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001333
tierno7edb6752016-03-21 17:37:52 +01001334 console_dict1 = console_dict.get("console")
1335 if console_dict1:
1336 console_url = console_dict1.get("url")
1337 if console_url:
1338 #parse console_url
1339 protocol_index = console_url.find("//")
1340 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1341 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1342 if protocol_index < 0 or port_index<0 or suffix_index<0:
1343 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1344 console_dict={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001345 "server": console_url[protocol_index+2:port_index],
1346 "port": console_url[port_index:suffix_index],
1347 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001348 }
1349 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001350 return console_dict
1351 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001352
tierno8e995ce2016-09-22 08:13:00 +00001353 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001354 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001355
tierno98e909c2017-10-14 13:27:03 +02001356 def delete_vminstance(self, vm_id, created_items=None):
tiernoae4a8d12016-07-08 12:30:39 +02001357 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001358 '''
tiernoae4a8d12016-07-08 12:30:39 +02001359 #print "osconnector: Getting VM from VIM"
tierno98e909c2017-10-14 13:27:03 +02001360 if created_items == None:
1361 created_items = {}
tierno7edb6752016-03-21 17:37:52 +01001362 try:
1363 self._reload_connection()
tierno98e909c2017-10-14 13:27:03 +02001364 # delete VM ports attached to this networks before the virtual machine
1365 for k, v in created_items.items():
1366 if not v: # skip already deleted
1367 continue
tierno7edb6752016-03-21 17:37:52 +01001368 try:
tiernoad6bdd42018-01-10 10:43:46 +01001369 k_item, _, k_id = k.partition(":")
1370 if k_item == "port":
1371 self.neutron.delete_port(k_id)
tierno7edb6752016-03-21 17:37:52 +01001372 except Exception as e:
tierno00e3df72017-11-29 17:20:13 +01001373 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
montesmoreno0c8def02016-12-22 12:16:23 +00001374
tierno98e909c2017-10-14 13:27:03 +02001375 # #commented because detaching the volumes makes the servers.delete not work properly ?!?
1376 # #dettach volumes attached
1377 # server = self.nova.servers.get(vm_id)
1378 # volumes_attached_dict = server._info['os-extended-volumes:volumes_attached'] #volume['id']
1379 # #for volume in volumes_attached_dict:
1380 # # self.cinder.volumes.detach(volume['id'])
montesmoreno0c8def02016-12-22 12:16:23 +00001381
tierno98e909c2017-10-14 13:27:03 +02001382 if vm_id:
1383 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001384
tierno98e909c2017-10-14 13:27:03 +02001385 # delete volumes. Although having detached, they should have in active status before deleting
1386 # we ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00001387 keep_waiting = True
1388 elapsed_time = 0
1389 while keep_waiting and elapsed_time < volume_timeout:
1390 keep_waiting = False
tierno98e909c2017-10-14 13:27:03 +02001391 for k, v in created_items.items():
1392 if not v: # skip already deleted
1393 continue
1394 try:
tiernoad6bdd42018-01-10 10:43:46 +01001395 k_item, _, k_id = k.partition(":")
1396 if k_item == "volume":
1397 if self.cinder.volumes.get(k_id).status != 'available':
tierno98e909c2017-10-14 13:27:03 +02001398 keep_waiting = True
1399 else:
tiernoad6bdd42018-01-10 10:43:46 +01001400 self.cinder.volumes.delete(k_id)
tierno98e909c2017-10-14 13:27:03 +02001401 except Exception as e:
tierno00e3df72017-11-29 17:20:13 +01001402 self.logger.error("Error deleting volume: {}: {}".format(type(e).__name__, e))
montesmoreno0c8def02016-12-22 12:16:23 +00001403 if keep_waiting:
1404 time.sleep(1)
1405 elapsed_time += 1
tierno98e909c2017-10-14 13:27:03 +02001406 return None
tierno8e995ce2016-09-22 08:13:00 +00001407 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001408 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001409
tiernoae4a8d12016-07-08 12:30:39 +02001410 def refresh_vms_status(self, vm_list):
1411 '''Get the status of the virtual machines and their interfaces/ports
1412 Params: the list of VM identifiers
1413 Returns a dictionary with:
1414 vm_id: #VIM id of this Virtual Machine
1415 status: #Mandatory. Text with one of:
1416 # DELETED (not found at vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001417 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
tiernoae4a8d12016-07-08 12:30:39 +02001418 # OTHER (Vim reported other status not understood)
1419 # ERROR (VIM indicates an ERROR status)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001420 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
tiernoae4a8d12016-07-08 12:30:39 +02001421 # CREATING (on building process), ERROR
1422 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1423 #
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001424 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
tiernoae4a8d12016-07-08 12:30:39 +02001425 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1426 interfaces:
1427 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1428 mac_address: #Text format XX:XX:XX:XX:XX:XX
1429 vim_net_id: #network id where this interface is connected
1430 vim_interface_id: #interface/port VIM id
1431 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001432 compute_node: #identification of compute node where PF,VF interface is allocated
1433 pci: #PCI address of the NIC that hosts the PF,VF
1434 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001435 '''
tiernoae4a8d12016-07-08 12:30:39 +02001436 vm_dict={}
1437 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1438 for vm_id in vm_list:
1439 vm={}
1440 try:
1441 vm_vim = self.get_vminstance(vm_id)
1442 if vm_vim['status'] in vmStatus2manoFormat:
1443 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001444 else:
tiernoae4a8d12016-07-08 12:30:39 +02001445 vm['status'] = "OTHER"
1446 vm['error_msg'] = "VIM status reported " + vm_vim['status']
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001447
1448 vm['vim_info'] = self.serialize(vm_vim)
1449
tiernoae4a8d12016-07-08 12:30:39 +02001450 vm["interfaces"] = []
1451 if vm_vim.get('fault'):
1452 vm['error_msg'] = str(vm_vim['fault'])
1453 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001454 try:
tiernoae4a8d12016-07-08 12:30:39 +02001455 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02001456 port_dict = self.neutron.list_ports(device_id=vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02001457 for port in port_dict["ports"]:
1458 interface={}
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001459 interface['vim_info'] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02001460 interface["mac_address"] = port.get("mac_address")
1461 interface["vim_net_id"] = port["network_id"]
1462 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001463 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04001464 # in case of non-admin credentials, it will be missing
1465 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1466 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001467 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001468
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001469 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04001470 # in case of non-admin credentials, it will be missing
1471 if port.get('binding:profile'):
1472 if port['binding:profile'].get('pci_slot'):
1473 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1474 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1475 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1476 pci = port['binding:profile']['pci_slot']
1477 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1478 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001479 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001480 #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 +01001481 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001482 if network['network'].get('provider:network_type') == 'vlan' and \
1483 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001484 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001485 ips=[]
1486 #look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02001487 try:
1488 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1489 if floating_ip_dict.get("floatingips"):
1490 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
1491 except Exception:
1492 pass
tierno7edb6752016-03-21 17:37:52 +01001493
tiernoae4a8d12016-07-08 12:30:39 +02001494 for subnet in port["fixed_ips"]:
1495 ips.append(subnet["ip_address"])
1496 interface["ip_address"] = ";".join(ips)
1497 vm["interfaces"].append(interface)
1498 except Exception as e:
tiernob42fd9b2018-06-20 10:44:32 +02001499 self.logger.error("Error getting vm interface information {}: {}".format(type(e).__name__, e),
1500 exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +02001501 except vimconn.vimconnNotFoundException as e:
1502 self.logger.error("Exception getting vm status: %s", str(e))
1503 vm['status'] = "DELETED"
1504 vm['error_msg'] = str(e)
1505 except vimconn.vimconnException as e:
1506 self.logger.error("Exception getting vm status: %s", str(e))
1507 vm['status'] = "VIM_ERROR"
1508 vm['error_msg'] = str(e)
1509 vm_dict[vm_id] = vm
1510 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001511
tierno98e909c2017-10-14 13:27:03 +02001512 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno7edb6752016-03-21 17:37:52 +01001513 '''Send and action over a VM instance from VIM
tierno98e909c2017-10-14 13:27:03 +02001514 Returns None or the console dict if the action was successfully sent to the VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001515 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001516 try:
1517 self._reload_connection()
1518 server = self.nova.servers.find(id=vm_id)
1519 if "start" in action_dict:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001520 if action_dict["start"]=="rebuild":
tierno7edb6752016-03-21 17:37:52 +01001521 server.rebuild()
1522 else:
1523 if server.status=="PAUSED":
1524 server.unpause()
1525 elif server.status=="SUSPENDED":
1526 server.resume()
1527 elif server.status=="SHUTOFF":
1528 server.start()
1529 elif "pause" in action_dict:
1530 server.pause()
1531 elif "resume" in action_dict:
1532 server.resume()
1533 elif "shutoff" in action_dict or "shutdown" in action_dict:
1534 server.stop()
1535 elif "forceOff" in action_dict:
1536 server.stop() #TODO
1537 elif "terminate" in action_dict:
1538 server.delete()
1539 elif "createImage" in action_dict:
1540 server.create_image()
1541 #"path":path_schema,
1542 #"description":description_schema,
1543 #"name":name_schema,
1544 #"metadata":metadata_schema,
1545 #"imageRef": id_schema,
1546 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1547 elif "rebuild" in action_dict:
1548 server.rebuild(server.image['id'])
1549 elif "reboot" in action_dict:
1550 server.reboot() #reboot_type='SOFT'
1551 elif "console" in action_dict:
1552 console_type = action_dict["console"]
1553 if console_type == None or console_type == "novnc":
1554 console_dict = server.get_vnc_console("novnc")
1555 elif console_type == "xvpvnc":
1556 console_dict = server.get_vnc_console(console_type)
1557 elif console_type == "rdp-html5":
1558 console_dict = server.get_rdp_console(console_type)
1559 elif console_type == "spice-html5":
1560 console_dict = server.get_spice_console(console_type)
1561 else:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001562 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
tiernoae4a8d12016-07-08 12:30:39 +02001563 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001564 try:
1565 console_url = console_dict["console"]["url"]
1566 #parse console_url
1567 protocol_index = console_url.find("//")
1568 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1569 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1570 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001571 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001572 console_dict2={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001573 "server": console_url[protocol_index+2 : port_index],
1574 "port": int(console_url[port_index+1 : suffix_index]),
1575 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001576 }
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001577 return console_dict2
tiernoae4a8d12016-07-08 12:30:39 +02001578 except Exception as e:
1579 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001580
tierno98e909c2017-10-14 13:27:03 +02001581 return None
tierno8e995ce2016-09-22 08:13:00 +00001582 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001583 self._format_exception(e)
1584 #TODO insert exception vimconn.HTTP_Unauthorized
1585
kate721d79b2017-06-24 04:21:38 -07001586 ####### VIO Specific Changes #########
1587 def _genrate_vlanID(self):
1588 """
1589 Method to get unused vlanID
1590 Args:
1591 None
1592 Returns:
1593 vlanID
1594 """
1595 #Get used VLAN IDs
1596 usedVlanIDs = []
1597 networks = self.get_network_list()
1598 for net in networks:
1599 if net.get('provider:segmentation_id'):
1600 usedVlanIDs.append(net.get('provider:segmentation_id'))
1601 used_vlanIDs = set(usedVlanIDs)
1602
1603 #find unused VLAN ID
1604 for vlanID_range in self.config.get('dataplane_net_vlan_range'):
1605 try:
1606 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
1607 for vlanID in xrange(start_vlanid, end_vlanid + 1):
1608 if vlanID not in used_vlanIDs:
1609 return vlanID
1610 except Exception as exp:
1611 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1612 else:
1613 raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1614 " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
1615
1616
1617 def _validate_vlan_ranges(self, dataplane_net_vlan_range):
1618 """
1619 Method to validate user given vlanID ranges
1620 Args: None
1621 Returns: None
1622 """
1623 for vlanID_range in dataplane_net_vlan_range:
1624 vlan_range = vlanID_range.replace(" ", "")
1625 #validate format
1626 vlanID_pattern = r'(\d)*-(\d)*$'
1627 match_obj = re.match(vlanID_pattern, vlan_range)
1628 if not match_obj:
1629 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1630 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range))
1631
1632 start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
1633 if start_vlanid <= 0 :
1634 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1635 "Start ID can not be zero. For VLAN "\
1636 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1637 if end_vlanid > 4094 :
1638 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1639 "End VLAN ID can not be greater than 4094. For VLAN "\
1640 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1641
1642 if start_vlanid > end_vlanid:
1643 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1644 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1645 "start_ID < end_ID ".format(vlanID_range))
1646
tiernoae4a8d12016-07-08 12:30:39 +02001647#NOT USED FUNCTIONS
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001648
tiernoae4a8d12016-07-08 12:30:39 +02001649 def new_external_port(self, port_data):
1650 #TODO openstack if needed
1651 '''Adds a external port to VIM'''
1652 '''Returns the port identifier'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001653 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1654
tiernoae4a8d12016-07-08 12:30:39 +02001655 def connect_port_network(self, port_id, network_id, admin=False):
1656 #TODO openstack if needed
1657 '''Connects a external port to a network'''
1658 '''Returns status code of the VIM response'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001659 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1660
tiernoae4a8d12016-07-08 12:30:39 +02001661 def new_user(self, user_name, user_passwd, tenant_id=None):
1662 '''Adds a new user to openstack VIM'''
1663 '''Returns the user identifier'''
1664 self.logger.debug("osconnector: Adding a new user to VIM")
1665 try:
1666 self._reload_connection()
Eduardo Sousae3c0dbc2018-09-03 11:56:07 +01001667 user=self.keystone.users.create(user_name, password=user_passwd, default_project=tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +02001668 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1669 return user.id
1670 except ksExceptions.ConnectionError as e:
1671 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 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001674 error_value=-vimconn.HTTP_Bad_Request
1675 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1676 #TODO insert exception vimconn.HTTP_Unauthorized
1677 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001678 self.logger.debug("new_user " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001679 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001680
1681 def delete_user(self, user_id):
1682 '''Delete a user from openstack VIM'''
1683 '''Returns the user identifier'''
1684 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001685 print("osconnector: Deleting a user from VIM")
tiernoae4a8d12016-07-08 12:30:39 +02001686 try:
1687 self._reload_connection()
1688 self.keystone.users.delete(user_id)
1689 return 1, user_id
1690 except ksExceptions.ConnectionError as e:
1691 error_value=-vimconn.HTTP_Bad_Request
1692 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1693 except ksExceptions.NotFound as e:
1694 error_value=-vimconn.HTTP_Not_Found
1695 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1696 except ksExceptions.ClientException as e: #TODO remove
1697 error_value=-vimconn.HTTP_Bad_Request
1698 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1699 #TODO insert exception vimconn.HTTP_Unauthorized
1700 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001701 self.logger.debug("delete_tenant " + error_text)
tiernoae4a8d12016-07-08 12:30:39 +02001702 return error_value, error_text
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001703
tierno7edb6752016-03-21 17:37:52 +01001704 def get_hosts_info(self):
1705 '''Get the information of deployed hosts
1706 Returns the hosts content'''
1707 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001708 print("osconnector: Getting Host info from VIM")
tierno7edb6752016-03-21 17:37:52 +01001709 try:
1710 h_list=[]
1711 self._reload_connection()
1712 hypervisors = self.nova.hypervisors.list()
1713 for hype in hypervisors:
1714 h_list.append( hype.to_dict() )
1715 return 1, {"hosts":h_list}
1716 except nvExceptions.NotFound as e:
1717 error_value=-vimconn.HTTP_Not_Found
1718 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1719 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1720 error_value=-vimconn.HTTP_Bad_Request
1721 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1722 #TODO insert exception vimconn.HTTP_Unauthorized
1723 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001724 self.logger.debug("get_hosts_info " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001725 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001726
1727 def get_hosts(self, vim_tenant):
1728 '''Get the hosts and deployed instances
1729 Returns the hosts content'''
1730 r, hype_dict = self.get_hosts_info()
1731 if r<0:
1732 return r, hype_dict
1733 hypervisors = hype_dict["hosts"]
1734 try:
1735 servers = self.nova.servers.list()
1736 for hype in hypervisors:
1737 for server in servers:
1738 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1739 if 'vm' in hype:
1740 hype['vm'].append(server.id)
1741 else:
1742 hype['vm'] = [server.id]
1743 return 1, hype_dict
1744 except nvExceptions.NotFound as e:
1745 error_value=-vimconn.HTTP_Not_Found
1746 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1747 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1748 error_value=-vimconn.HTTP_Bad_Request
1749 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1750 #TODO insert exception vimconn.HTTP_Unauthorized
1751 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001752 self.logger.debug("get_hosts " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001753 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001754
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001755 def new_classification(self, name, ctype, definition):
1756 self.logger.debug(
1757 'Adding a new (Traffic) Classification to VIM, named %s', name)
1758 try:
1759 new_class = None
1760 self._reload_connection()
1761 if ctype not in supportedClassificationTypes:
1762 raise vimconn.vimconnNotSupportedException(
1763 'OpenStack VIM connector doesn\'t support provided '
1764 'Classification Type {}, supported ones are: '
1765 '{}'.format(ctype, supportedClassificationTypes))
1766 if not self._validate_classification(ctype, definition):
1767 raise vimconn.vimconnException(
1768 'Incorrect Classification definition '
1769 'for the type specified.')
1770 classification_dict = definition
1771 classification_dict['name'] = name
tierno7edb6752016-03-21 17:37:52 +01001772
Igor D.Ccaadc442017-11-06 12:48:48 +00001773 new_class = self.neutron.create_sfc_flow_classifier(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001774 {'flow_classifier': classification_dict})
1775 return new_class['flow_classifier']['id']
1776 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1777 neExceptions.NeutronException, ConnectionError) as e:
1778 self.logger.error(
1779 'Creation of Classification failed.')
1780 self._format_exception(e)
1781
1782 def get_classification(self, class_id):
1783 self.logger.debug(" Getting Classification %s from VIM", class_id)
1784 filter_dict = {"id": class_id}
1785 class_list = self.get_classification_list(filter_dict)
1786 if len(class_list) == 0:
1787 raise vimconn.vimconnNotFoundException(
1788 "Classification '{}' not found".format(class_id))
1789 elif len(class_list) > 1:
1790 raise vimconn.vimconnConflictException(
1791 "Found more than one Classification with this criteria")
1792 classification = class_list[0]
1793 return classification
1794
1795 def get_classification_list(self, filter_dict={}):
1796 self.logger.debug("Getting Classifications from VIM filter: '%s'",
1797 str(filter_dict))
1798 try:
tierno69b590e2018-03-13 18:52:23 +01001799 filter_dict_os = filter_dict.copy()
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001800 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001801 if self.api_version3 and "tenant_id" in filter_dict_os:
1802 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
Igor D.Ccaadc442017-11-06 12:48:48 +00001803 classification_dict = self.neutron.list_sfc_flow_classifiers(
tierno69b590e2018-03-13 18:52:23 +01001804 **filter_dict_os)
1805 classification_list = classification_dict["flow_classifiers"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001806 self.__classification_os2mano(classification_list)
1807 return classification_list
1808 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1809 neExceptions.NeutronException, ConnectionError) as e:
1810 self._format_exception(e)
1811
1812 def delete_classification(self, class_id):
1813 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
1814 try:
1815 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001816 self.neutron.delete_sfc_flow_classifier(class_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001817 return class_id
1818 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1819 ksExceptions.ClientException, neExceptions.NeutronException,
1820 ConnectionError) as e:
1821 self._format_exception(e)
1822
1823 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
1824 self.logger.debug(
1825 "Adding a new Service Function Instance to VIM, named '%s'", name)
1826 try:
1827 new_sfi = None
1828 self._reload_connection()
1829 correlation = None
1830 if sfc_encap:
Igor D.Ccaadc442017-11-06 12:48:48 +00001831 correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001832 if len(ingress_ports) != 1:
1833 raise vimconn.vimconnNotSupportedException(
1834 "OpenStack VIM connector can only have "
1835 "1 ingress port per SFI")
1836 if len(egress_ports) != 1:
1837 raise vimconn.vimconnNotSupportedException(
1838 "OpenStack VIM connector can only have "
1839 "1 egress port per SFI")
1840 sfi_dict = {'name': name,
1841 'ingress': ingress_ports[0],
1842 'egress': egress_ports[0],
1843 'service_function_parameters': {
1844 'correlation': correlation}}
Igor D.Ccaadc442017-11-06 12:48:48 +00001845 new_sfi = self.neutron.create_sfc_port_pair({'port_pair': sfi_dict})
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001846 return new_sfi['port_pair']['id']
1847 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1848 neExceptions.NeutronException, ConnectionError) as e:
1849 if new_sfi:
1850 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00001851 self.neutron.delete_sfc_port_pair(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001852 new_sfi['port_pair']['id'])
1853 except Exception:
1854 self.logger.error(
1855 'Creation of Service Function Instance failed, with '
1856 'subsequent deletion failure as well.')
1857 self._format_exception(e)
1858
1859 def get_sfi(self, sfi_id):
1860 self.logger.debug(
1861 'Getting Service Function Instance %s from VIM', sfi_id)
1862 filter_dict = {"id": sfi_id}
1863 sfi_list = self.get_sfi_list(filter_dict)
1864 if len(sfi_list) == 0:
1865 raise vimconn.vimconnNotFoundException(
1866 "Service Function Instance '{}' not found".format(sfi_id))
1867 elif len(sfi_list) > 1:
1868 raise vimconn.vimconnConflictException(
1869 'Found more than one Service Function Instance '
1870 'with this criteria')
1871 sfi = sfi_list[0]
1872 return sfi
1873
1874 def get_sfi_list(self, filter_dict={}):
1875 self.logger.debug("Getting Service Function Instances from "
1876 "VIM filter: '%s'", str(filter_dict))
1877 try:
1878 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001879 filter_dict_os = filter_dict.copy()
1880 if self.api_version3 and "tenant_id" in filter_dict_os:
1881 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
1882 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001883 sfi_list = sfi_dict["port_pairs"]
1884 self.__sfi_os2mano(sfi_list)
1885 return sfi_list
1886 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1887 neExceptions.NeutronException, ConnectionError) as e:
1888 self._format_exception(e)
1889
1890 def delete_sfi(self, sfi_id):
1891 self.logger.debug("Deleting Service Function Instance '%s' "
1892 "from VIM", sfi_id)
1893 try:
1894 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001895 self.neutron.delete_sfc_port_pair(sfi_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001896 return sfi_id
1897 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1898 ksExceptions.ClientException, neExceptions.NeutronException,
1899 ConnectionError) as e:
1900 self._format_exception(e)
1901
1902 def new_sf(self, name, sfis, sfc_encap=True):
1903 self.logger.debug("Adding a new Service Function to VIM, "
1904 "named '%s'", name)
1905 try:
1906 new_sf = None
1907 self._reload_connection()
tierno9c5c8322018-03-23 15:44:03 +01001908 # correlation = None
1909 # if sfc_encap:
1910 # correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001911 for instance in sfis:
1912 sfi = self.get_sfi(instance)
Igor D.Ccaadc442017-11-06 12:48:48 +00001913 if sfi.get('sfc_encap') != sfc_encap:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001914 raise vimconn.vimconnNotSupportedException(
1915 "OpenStack VIM connector requires all SFIs of the "
1916 "same SF to share the same SFC Encapsulation")
1917 sf_dict = {'name': name,
1918 'port_pairs': sfis}
Igor D.Ccaadc442017-11-06 12:48:48 +00001919 new_sf = self.neutron.create_sfc_port_pair_group({
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001920 'port_pair_group': sf_dict})
1921 return new_sf['port_pair_group']['id']
1922 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1923 neExceptions.NeutronException, ConnectionError) as e:
1924 if new_sf:
1925 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00001926 self.neutron.delete_sfc_port_pair_group(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001927 new_sf['port_pair_group']['id'])
1928 except Exception:
1929 self.logger.error(
1930 'Creation of Service Function failed, with '
1931 'subsequent deletion failure as well.')
1932 self._format_exception(e)
1933
1934 def get_sf(self, sf_id):
1935 self.logger.debug("Getting Service Function %s from VIM", sf_id)
1936 filter_dict = {"id": sf_id}
1937 sf_list = self.get_sf_list(filter_dict)
1938 if len(sf_list) == 0:
1939 raise vimconn.vimconnNotFoundException(
1940 "Service Function '{}' not found".format(sf_id))
1941 elif len(sf_list) > 1:
1942 raise vimconn.vimconnConflictException(
1943 "Found more than one Service Function with this criteria")
1944 sf = sf_list[0]
1945 return sf
1946
1947 def get_sf_list(self, filter_dict={}):
1948 self.logger.debug("Getting Service Function from VIM filter: '%s'",
1949 str(filter_dict))
1950 try:
1951 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001952 filter_dict_os = filter_dict.copy()
1953 if self.api_version3 and "tenant_id" in filter_dict_os:
1954 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
1955 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001956 sf_list = sf_dict["port_pair_groups"]
1957 self.__sf_os2mano(sf_list)
1958 return sf_list
1959 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1960 neExceptions.NeutronException, ConnectionError) as e:
1961 self._format_exception(e)
1962
1963 def delete_sf(self, sf_id):
1964 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
1965 try:
1966 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001967 self.neutron.delete_sfc_port_pair_group(sf_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001968 return sf_id
1969 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1970 ksExceptions.ClientException, neExceptions.NeutronException,
1971 ConnectionError) as e:
1972 self._format_exception(e)
1973
1974 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
1975 self.logger.debug("Adding a new Service Function Path to VIM, "
1976 "named '%s'", name)
1977 try:
1978 new_sfp = None
1979 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00001980 # In networking-sfc the MPLS encapsulation is legacy
1981 # should be used when no full SFC Encapsulation is intended
schillinge981df9a2019-01-24 09:25:11 +01001982 correlation = 'mpls'
Igor D.Ccaadc442017-11-06 12:48:48 +00001983 if sfc_encap:
1984 correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001985 sfp_dict = {'name': name,
1986 'flow_classifiers': classifications,
1987 'port_pair_groups': sfs,
1988 'chain_parameters': {'correlation': correlation}}
1989 if spi:
1990 sfp_dict['chain_id'] = spi
Igor D.Ccaadc442017-11-06 12:48:48 +00001991 new_sfp = self.neutron.create_sfc_port_chain({'port_chain': sfp_dict})
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001992 return new_sfp["port_chain"]["id"]
1993 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1994 neExceptions.NeutronException, ConnectionError) as e:
1995 if new_sfp:
1996 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00001997 self.neutron.delete_sfc_port_chain(new_sfp['port_chain']['id'])
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001998 except Exception:
1999 self.logger.error(
2000 'Creation of Service Function Path failed, with '
2001 'subsequent deletion failure as well.')
2002 self._format_exception(e)
2003
2004 def get_sfp(self, sfp_id):
2005 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
2006 filter_dict = {"id": sfp_id}
2007 sfp_list = self.get_sfp_list(filter_dict)
2008 if len(sfp_list) == 0:
2009 raise vimconn.vimconnNotFoundException(
2010 "Service Function Path '{}' not found".format(sfp_id))
2011 elif len(sfp_list) > 1:
2012 raise vimconn.vimconnConflictException(
2013 "Found more than one Service Function Path with this criteria")
2014 sfp = sfp_list[0]
2015 return sfp
2016
2017 def get_sfp_list(self, filter_dict={}):
2018 self.logger.debug("Getting Service Function Paths from VIM filter: "
2019 "'%s'", str(filter_dict))
2020 try:
2021 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01002022 filter_dict_os = filter_dict.copy()
2023 if self.api_version3 and "tenant_id" in filter_dict_os:
2024 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
2025 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002026 sfp_list = sfp_dict["port_chains"]
2027 self.__sfp_os2mano(sfp_list)
2028 return sfp_list
2029 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2030 neExceptions.NeutronException, ConnectionError) as e:
2031 self._format_exception(e)
2032
2033 def delete_sfp(self, sfp_id):
2034 self.logger.debug(
2035 "Deleting Service Function Path '%s' from VIM", sfp_id)
2036 try:
2037 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00002038 self.neutron.delete_sfc_port_chain(sfp_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002039 return sfp_id
2040 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2041 ksExceptions.ClientException, neExceptions.NeutronException,
2042 ConnectionError) as e:
2043 self._format_exception(e)