blob: ff43346064f66f0ec146f74236470321c553386a [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.
tierno7edb6752016-03-21 17:37:52 +010019##
20
21'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000022osconnector implements all the methods to interact with openstack using the python-neutronclient.
23
24For the VNF forwarding graph, The OpenStack VIM connector calls the
25networking-sfc Neutron extension methods, whose resources are mapped
26to the VIM connector's SFC resources as follows:
27- Classification (OSM) -> Flow Classifier (Neutron)
28- Service Function Instance (OSM) -> Port Pair (Neutron)
29- Service Function (OSM) -> Port Pair Group (Neutron)
30- Service Function Path (OSM) -> Port Chain (Neutron)
tierno7edb6752016-03-21 17:37:52 +010031'''
Eduardo Sousae3c0dbc2018-09-03 11:56:07 +010032__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C., Eduardo Sousa"
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000033__date__ = "$22-sep-2017 23:59:59$"
tierno7edb6752016-03-21 17:37:52 +010034
tierno7d782ef2019-10-04 12:56:31 +000035from osm_ro import vimconn
tierno69b590e2018-03-13 18:52:23 +010036# import json
tiernoae4a8d12016-07-08 12:30:39 +020037import logging
garciadeblas9f8456e2016-09-05 05:02:59 +020038import netaddr
montesmoreno0c8def02016-12-22 12:16:23 +000039import time
tierno36c0b172017-01-12 18:32:28 +010040import yaml
garciadeblas2299e3b2017-01-26 14:35:55 +000041import random
kate721d79b2017-06-24 04:21:38 -070042import re
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000043import copy
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010044from pprint import pformat
tierno7edb6752016-03-21 17:37:52 +010045
tiernob5cef372017-06-19 15:52:22 +020046from novaclient import client as nClient, exceptions as nvExceptions
47from keystoneauth1.identity import v2, v3
48from keystoneauth1 import session
tierno7edb6752016-03-21 17:37:52 +010049import keystoneclient.exceptions as ksExceptions
tiernof716aea2017-06-21 18:01:40 +020050import keystoneclient.v3.client as ksClient_v3
51import keystoneclient.v2_0.client as ksClient_v2
tiernob5cef372017-06-19 15:52:22 +020052from glanceclient import client as glClient
tierno7edb6752016-03-21 17:37:52 +010053import glanceclient.exc as gl1Exceptions
tiernob5cef372017-06-19 15:52:22 +020054from cinderclient import client as cClient
tierno7d782ef2019-10-04 12:56:31 +000055from http.client import HTTPException # TODO py3 check that this base exception matches python2 httplib.HTTPException
tiernob5cef372017-06-19 15:52:22 +020056from neutronclient.neutron import client as neClient
tierno7edb6752016-03-21 17:37:52 +010057from neutronclient.common import exceptions as neExceptions
58from requests.exceptions import ConnectionError
59
tierno40e1bce2017-08-09 09:12:04 +020060
61"""contain the openstack virtual machine status to openmano status"""
tierno7edb6752016-03-21 17:37:52 +010062vmStatus2manoFormat={'ACTIVE':'ACTIVE',
63 'PAUSED':'PAUSED',
64 'SUSPENDED': 'SUSPENDED',
65 'SHUTOFF':'INACTIVE',
66 'BUILD':'BUILD',
67 'ERROR':'ERROR','DELETED':'DELETED'
68 }
69netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
70 }
71
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000072supportedClassificationTypes = ['legacy_flow_classifier']
73
montesmoreno0c8def02016-12-22 12:16:23 +000074#global var to have a timeout creating and deleting volumes
tierno00e3df72017-11-29 17:20:13 +010075volume_timeout = 600
76server_timeout = 600
montesmoreno0c8def02016-12-22 12:16:23 +000077
Anderson Bravalheri0446cd52018-08-17 15:26:19 +010078
79class SafeDumper(yaml.SafeDumper):
80 def represent_data(self, data):
81 # Openstack APIs use custom subclasses of dict and YAML safe dumper
82 # is designed to not handle that (reference issue 142 of pyyaml)
83 if isinstance(data, dict) and data.__class__ != dict:
84 # A simple solution is to convert those items back to dicts
85 data = dict(data.items())
86
87 return super(SafeDumper, self).represent_data(data)
88
89
tierno7edb6752016-03-21 17:37:52 +010090class vimconnector(vimconn.vimconnector):
tiernob3d36742017-03-03 23:51:05 +010091 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
92 log_level=None, config={}, persistent_info={}):
ahmadsa96af9f42017-01-31 16:17:14 +050093 '''using common constructor parameters. In this case
tierno7edb6752016-03-21 17:37:52 +010094 'url' is the keystone authorization url,
95 'url_admin' is not use
96 '''
tiernof716aea2017-06-21 18:01:40 +020097 api_version = config.get('APIversion')
98 if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
tiernob5cef372017-06-19 15:52:22 +020099 raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
tiernof716aea2017-06-21 18:01:40 +0200100 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
kate721d79b2017-06-24 04:21:38 -0700101 vim_type = config.get('vim_type')
102 if vim_type and vim_type not in ('vio', 'VIO'):
103 raise vimconn.vimconnException("Invalid value '{}' for config:vim_type."
104 "Allowed values are 'vio' or 'VIO'".format(vim_type))
105
106 if config.get('dataplane_net_vlan_range') is not None:
107 #validate vlan ranges provided by user
garciadeblasebd66722019-01-31 16:01:31 +0000108 self._validate_vlan_ranges(config.get('dataplane_net_vlan_range'), 'dataplane_net_vlan_range')
109
110 if config.get('multisegment_vlan_range') is not None:
111 #validate vlan ranges provided by user
112 self._validate_vlan_ranges(config.get('multisegment_vlan_range'), 'multisegment_vlan_range')
kate721d79b2017-06-24 04:21:38 -0700113
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})
tiernoa05b65a2019-02-01 12:30:27 +0000130 self.my_tenant_id = self.session.get('my_tenant_id')
tiernob5cef372017-06-19 15:52:22 +0200131 self.nova = self.session.get('nova')
132 self.neutron = self.session.get('neutron')
133 self.cinder = self.session.get('cinder')
134 self.glance = self.session.get('glance')
tierno1beea862018-07-11 15:47:37 +0200135 # self.glancev1 = self.session.get('glancev1')
tiernof716aea2017-06-21 18:01:40 +0200136 self.keystone = self.session.get('keystone')
137 self.api_version3 = self.session.get('api_version3')
kate721d79b2017-06-24 04:21:38 -0700138 self.vim_type = self.config.get("vim_type")
139 if self.vim_type:
140 self.vim_type = self.vim_type.upper()
141 if self.config.get("use_internal_endpoint"):
142 self.endpoint_type = "internalURL"
143 else:
144 self.endpoint_type = None
montesmoreno0c8def02016-12-22 12:16:23 +0000145
garciadeblasa0a356e2019-12-18 09:32:09 +0100146 logging.getLogger('urllib3').setLevel(logging.WARNING)
147 logging.getLogger('keystoneauth').setLevel(logging.WARNING)
148 logging.getLogger('novaclient').setLevel(logging.WARNING)
tierno73ad9e42016-09-12 18:11:11 +0200149 self.logger = logging.getLogger('openmano.vim.openstack')
kate721d79b2017-06-24 04:21:38 -0700150
tiernoa05b65a2019-02-01 12:30:27 +0000151 # allow security_groups to be a list or a single string
152 if isinstance(self.config.get('security_groups'), str):
153 self.config['security_groups'] = [self.config['security_groups']]
154 self.security_groups_id = None
155
kate721d79b2017-06-24 04:21:38 -0700156 ####### VIO Specific Changes #########
157 if self.vim_type == "VIO":
158 self.logger = logging.getLogger('openmano.vim.vio')
159
tiernofe789902016-09-29 14:20:44 +0000160 if log_level:
kate54616752017-09-05 23:26:28 -0700161 self.logger.setLevel( getattr(logging, log_level))
tiernof716aea2017-06-21 18:01:40 +0200162
163 def __getitem__(self, index):
164 """Get individuals parameters.
165 Throw KeyError"""
166 if index == 'project_domain_id':
167 return self.config.get("project_domain_id")
168 elif index == 'user_domain_id':
169 return self.config.get("user_domain_id")
170 else:
tierno76a3c312017-06-29 16:42:15 +0200171 return vimconn.vimconnector.__getitem__(self, index)
tiernof716aea2017-06-21 18:01:40 +0200172
173 def __setitem__(self, index, value):
174 """Set individuals parameters and it is marked as dirty so to force connection reload.
175 Throw KeyError"""
176 if index == 'project_domain_id':
177 self.config["project_domain_id"] = value
178 elif index == 'user_domain_id':
179 self.config["user_domain_id"] = value
180 else:
181 vimconn.vimconnector.__setitem__(self, index, value)
tiernob5cef372017-06-19 15:52:22 +0200182 self.session['reload_client'] = True
tiernof716aea2017-06-21 18:01:40 +0200183
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100184 def serialize(self, value):
185 """Serialization of python basic types.
186
187 In the case value is not serializable a message will be logged and a
188 simple representation of the data that cannot be converted back to
189 python is returned.
190 """
tierno7d782ef2019-10-04 12:56:31 +0000191 if isinstance(value, str):
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100192 return value
193
194 try:
195 return yaml.dump(value, Dumper=SafeDumper,
196 default_flow_style=True, width=256)
197 except yaml.representer.RepresenterError:
tierno7d782ef2019-10-04 12:56:31 +0000198 self.logger.debug('The following entity cannot be serialized in YAML:\n\n%s\n\n', pformat(value),
199 exc_info=True)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100200 return str(value)
201
tierno7edb6752016-03-21 17:37:52 +0100202 def _reload_connection(self):
203 '''Called before any operation, it check if credentials has changed
204 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
205 '''
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100206 #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 +0200207 if self.session['reload_client']:
tiernof716aea2017-06-21 18:01:40 +0200208 if self.config.get('APIversion'):
209 self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3'
210 else: # get from ending auth_url that end with v3 or with v2.0
tierno3cb8dc32017-10-24 18:13:19 +0200211 self.api_version3 = self.url.endswith("/v3") or self.url.endswith("/v3/")
tiernof716aea2017-06-21 18:01:40 +0200212 self.session['api_version3'] = self.api_version3
213 if self.api_version3:
tierno3cb8dc32017-10-24 18:13:19 +0200214 if self.config.get('project_domain_id') or self.config.get('project_domain_name'):
215 project_domain_id_default = None
216 else:
217 project_domain_id_default = 'default'
218 if self.config.get('user_domain_id') or self.config.get('user_domain_name'):
219 user_domain_id_default = None
220 else:
221 user_domain_id_default = 'default'
tiernof716aea2017-06-21 18:01:40 +0200222 auth = v3.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200223 username=self.user,
224 password=self.passwd,
225 project_name=self.tenant_name,
226 project_id=self.tenant_id,
tierno3cb8dc32017-10-24 18:13:19 +0200227 project_domain_id=self.config.get('project_domain_id', project_domain_id_default),
228 user_domain_id=self.config.get('user_domain_id', user_domain_id_default),
229 project_domain_name=self.config.get('project_domain_name'),
230 user_domain_name=self.config.get('user_domain_name'))
ahmadsa95baa272016-11-30 09:14:11 +0500231 else:
tiernof716aea2017-06-21 18:01:40 +0200232 auth = v2.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200233 username=self.user,
234 password=self.passwd,
235 tenant_name=self.tenant_name,
236 tenant_id=self.tenant_id)
tierno4d1ce222018-04-06 10:41:06 +0200237 sess = session.Session(auth=auth, verify=self.verify)
fatollahy40c6a3f2019-02-19 12:53:40 +0000238 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River Titanium cloud and StarlingX
239 region_name = self.config.get('region_name')
tiernof716aea2017-06-21 18:01:40 +0200240 if self.api_version3:
fatollahy40c6a3f2019-02-19 12:53:40 +0000241 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
tiernof716aea2017-06-21 18:01:40 +0200242 else:
kate721d79b2017-06-24 04:21:38 -0700243 self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200244 self.session['keystone'] = self.keystone
montesmoreno9317d302017-08-16 12:48:23 +0200245 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
246 # This implementation approach is due to the warning message in
247 # https://developer.openstack.org/api-guide/compute/microversions.html
248 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
249 # always require an specific microversion.
250 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
251 version = self.config.get("microversion")
252 if not version:
253 version = "2.1"
fatollahy40c6a3f2019-02-19 12:53:40 +0000254 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River Titanium cloud and StarlingX
255 self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
256 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
257 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
tiernoa05b65a2019-02-01 12:30:27 +0000258 try:
259 self.my_tenant_id = self.session['my_tenant_id'] = sess.get_project_id()
260 except Exception as e:
261 self.logger.error("Cannot get project_id from session", exc_info=True)
kate721d79b2017-06-24 04:21:38 -0700262 if self.endpoint_type == "internalURL":
263 glance_service_id = self.keystone.services.list(name="glance")[0].id
264 glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
265 else:
266 glance_endpoint = None
267 self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
tiernoa05b65a2019-02-01 12:30:27 +0000268 # using version 1 of glance client in new_image()
tierno1beea862018-07-11 15:47:37 +0200269 # self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
270 # endpoint=glance_endpoint)
tiernob5cef372017-06-19 15:52:22 +0200271 self.session['reload_client'] = False
272 self.persistent_info['session'] = self.session
mirabal29356312017-07-27 12:21:22 +0200273 # add availablity zone info inside self.persistent_info
274 self._set_availablity_zones()
275 self.persistent_info['availability_zone'] = self.availability_zone
tiernoa05b65a2019-02-01 12:30:27 +0000276 self.security_groups_id = None # force to get again security_groups_ids next time they are needed
ahmadsa95baa272016-11-30 09:14:11 +0500277
tierno7edb6752016-03-21 17:37:52 +0100278 def __net_os2mano(self, net_list_dict):
279 '''Transform the net openstack format to mano format
280 net_list_dict can be a list of dict or a single dict'''
281 if type(net_list_dict) is dict:
282 net_list_=(net_list_dict,)
283 elif type(net_list_dict) is list:
284 net_list_=net_list_dict
285 else:
286 raise TypeError("param net_list_dict must be a list or a dictionary")
287 for net in net_list_:
288 if net.get('provider:network_type') == "vlan":
289 net['type']='data'
290 else:
291 net['type']='bridge'
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200292
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000293 def __classification_os2mano(self, class_list_dict):
294 """Transform the openstack format (Flow Classifier) to mano format
295 (Classification) class_list_dict can be a list of dict or a single dict
296 """
297 if isinstance(class_list_dict, dict):
298 class_list_ = [class_list_dict]
299 elif isinstance(class_list_dict, list):
300 class_list_ = class_list_dict
301 else:
302 raise TypeError(
303 "param class_list_dict must be a list or a dictionary")
304 for classification in class_list_:
305 id = classification.pop('id')
306 name = classification.pop('name')
307 description = classification.pop('description')
308 project_id = classification.pop('project_id')
309 tenant_id = classification.pop('tenant_id')
310 original_classification = copy.deepcopy(classification)
311 classification.clear()
312 classification['ctype'] = 'legacy_flow_classifier'
313 classification['definition'] = original_classification
314 classification['id'] = id
315 classification['name'] = name
316 classification['description'] = description
317 classification['project_id'] = project_id
318 classification['tenant_id'] = tenant_id
319
320 def __sfi_os2mano(self, sfi_list_dict):
321 """Transform the openstack format (Port Pair) to mano format (SFI)
322 sfi_list_dict can be a list of dict or a single dict
323 """
324 if isinstance(sfi_list_dict, dict):
325 sfi_list_ = [sfi_list_dict]
326 elif isinstance(sfi_list_dict, list):
327 sfi_list_ = sfi_list_dict
328 else:
329 raise TypeError(
330 "param sfi_list_dict must be a list or a dictionary")
331 for sfi in sfi_list_:
332 sfi['ingress_ports'] = []
333 sfi['egress_ports'] = []
334 if sfi.get('ingress'):
335 sfi['ingress_ports'].append(sfi['ingress'])
336 if sfi.get('egress'):
337 sfi['egress_ports'].append(sfi['egress'])
338 del sfi['ingress']
339 del sfi['egress']
340 params = sfi.get('service_function_parameters')
341 sfc_encap = False
342 if params:
343 correlation = params.get('correlation')
344 if correlation:
345 sfc_encap = True
346 sfi['sfc_encap'] = sfc_encap
347 del sfi['service_function_parameters']
348
349 def __sf_os2mano(self, sf_list_dict):
350 """Transform the openstack format (Port Pair Group) to mano format (SF)
351 sf_list_dict can be a list of dict or a single dict
352 """
353 if isinstance(sf_list_dict, dict):
354 sf_list_ = [sf_list_dict]
355 elif isinstance(sf_list_dict, list):
356 sf_list_ = sf_list_dict
357 else:
358 raise TypeError(
359 "param sf_list_dict must be a list or a dictionary")
360 for sf in sf_list_:
361 del sf['port_pair_group_parameters']
362 sf['sfis'] = sf['port_pairs']
363 del sf['port_pairs']
364
365 def __sfp_os2mano(self, sfp_list_dict):
366 """Transform the openstack format (Port Chain) to mano format (SFP)
367 sfp_list_dict can be a list of dict or a single dict
368 """
369 if isinstance(sfp_list_dict, dict):
370 sfp_list_ = [sfp_list_dict]
371 elif isinstance(sfp_list_dict, list):
372 sfp_list_ = sfp_list_dict
373 else:
374 raise TypeError(
375 "param sfp_list_dict must be a list or a dictionary")
376 for sfp in sfp_list_:
377 params = sfp.pop('chain_parameters')
378 sfc_encap = False
379 if params:
380 correlation = params.get('correlation')
381 if correlation:
382 sfc_encap = True
383 sfp['sfc_encap'] = sfc_encap
384 sfp['spi'] = sfp.pop('chain_id')
385 sfp['classifications'] = sfp.pop('flow_classifiers')
386 sfp['service_functions'] = sfp.pop('port_pair_groups')
387
388 # placeholder for now; read TODO note below
389 def _validate_classification(self, type, definition):
390 # only legacy_flow_classifier Type is supported at this point
391 return True
392 # TODO(igordcard): this method should be an abstract method of an
393 # abstract Classification class to be implemented by the specific
394 # Types. Also, abstract vimconnector should call the validation
395 # method before the implemented VIM connectors are called.
396
tiernoae4a8d12016-07-08 12:30:39 +0200397 def _format_exception(self, exception):
398 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
tiernode12f782019-04-05 12:46:42 +0000399
tiernode12f782019-04-05 12:46:42 +0000400 message_error = exception.message
tiernode12f782019-04-05 12:46:42 +0000401
402 if isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound, ksExceptions.NotFound,
403 gl1Exceptions.HTTPNotFound)):
404 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + message_error)
shashankjain3c83a212018-10-04 13:05:46 +0530405 elif isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
406 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed)):
tiernode12f782019-04-05 12:46:42 +0000407 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + message_error)
shashankjain3c83a212018-10-04 13:05:46 +0530408 elif isinstance(exception, (KeyError, nvExceptions.BadRequest, ksExceptions.BadRequest)):
tiernode12f782019-04-05 12:46:42 +0000409 raise vimconn.vimconnException(type(exception).__name__ + ": " + message_error)
anwarsc76a3ee2018-10-04 14:05:32 +0530410 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
411 neExceptions.NeutronException)):
tiernode12f782019-04-05 12:46:42 +0000412 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + message_error)
tiernoae4a8d12016-07-08 12:30:39 +0200413 elif isinstance(exception, nvExceptions.Conflict):
tiernode12f782019-04-05 12:46:42 +0000414 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + message_error)
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200415 elif isinstance(exception, vimconn.vimconnException):
tierno41a69812018-02-16 14:34:33 +0100416 raise exception
tiernof716aea2017-06-21 18:01:40 +0200417 else: # ()
tiernode12f782019-04-05 12:46:42 +0000418 self.logger.error("General Exception " + message_error, exc_info=True)
419 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + message_error)
tiernoae4a8d12016-07-08 12:30:39 +0200420
tiernoa05b65a2019-02-01 12:30:27 +0000421 def _get_ids_from_name(self):
422 """
423 Obtain ids from name of tenant and security_groups. Store at self .security_groups_id"
424 :return: None
425 """
426 # get tenant_id if only tenant_name is supplied
427 self._reload_connection()
428 if not self.my_tenant_id:
429 raise vimconn.vimconnConnectionException("Error getting tenant information from name={} id={}".
430 format(self.tenant_name, self.tenant_id))
431 if self.config.get('security_groups') and not self.security_groups_id:
432 # convert from name to id
433 neutron_sg_list = self.neutron.list_security_groups(tenant_id=self.my_tenant_id)["security_groups"]
434
435 self.security_groups_id = []
436 for sg in self.config.get('security_groups'):
437 for neutron_sg in neutron_sg_list:
438 if sg in (neutron_sg["id"], neutron_sg["name"]):
439 self.security_groups_id.append(neutron_sg["id"])
440 break
441 else:
442 self.security_groups_id = None
443 raise vimconn.vimconnConnectionException("Not found security group {} for this tenant".format(sg))
444
tierno5509c2e2019-07-04 16:23:20 +0000445 def check_vim_connectivity(self):
446 # just get network list to check connectivity and credentials
447 self.get_network_list(filter_dict={})
448
tiernoae4a8d12016-07-08 12:30:39 +0200449 def get_tenant_list(self, filter_dict={}):
450 '''Obtain tenants of VIM
451 filter_dict can contain the following keys:
452 name: filter by tenant name
453 id: filter by tenant uuid/id
454 <other VIM specific>
455 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
456 '''
ahmadsa95baa272016-11-30 09:14:11 +0500457 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200458 try:
459 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200460 if self.api_version3:
461 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500462 else:
tiernof716aea2017-06-21 18:01:40 +0200463 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500464 project_list=[]
465 for project in project_class_list:
tiernof716aea2017-06-21 18:01:40 +0200466 if filter_dict.get('id') and filter_dict["id"] != project.id:
467 continue
ahmadsa95baa272016-11-30 09:14:11 +0500468 project_list.append(project.to_dict())
469 return project_list
tiernof716aea2017-06-21 18:01:40 +0200470 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200471 self._format_exception(e)
472
473 def new_tenant(self, tenant_name, tenant_description):
474 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
475 self.logger.debug("Adding a new tenant name: %s", tenant_name)
476 try:
477 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200478 if self.api_version3:
479 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
480 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500481 else:
tiernof716aea2017-06-21 18:01:40 +0200482 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500483 return project.id
shashankjain3c83a212018-10-04 13:05:46 +0530484 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200485 self._format_exception(e)
486
487 def delete_tenant(self, tenant_id):
488 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
489 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
490 try:
491 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200492 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500493 self.keystone.projects.delete(tenant_id)
494 else:
495 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200496 return tenant_id
tierno6869ae72020-01-09 17:37:34 +0000497 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200498 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500499
tierno6869ae72020-01-09 17:37:34 +0000500 def new_network(self, net_name, net_type, ip_profile=None, shared=False, provider_network_profile=None):
garciadeblasebd66722019-01-31 16:01:31 +0000501 """Adds a tenant network to VIM
502 Params:
503 'net_name': name of the network
504 'net_type': one of:
505 'bridge': overlay isolated network
506 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
507 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
508 'ip_profile': is a dict containing the IP parameters of the network
509 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
510 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
511 'gateway_address': (Optional) ip_schema, that is X.X.X.X
512 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
513 'dhcp_enabled': True or False
514 'dhcp_start_address': ip_schema, first IP to grant
515 'dhcp_count': number of IPs to grant.
516 'shared': if this network can be seen/use by other tenants/organization
garciadeblas4af0d542020-02-18 16:01:13 +0100517 'provider_network_profile': (optional) contains {segmentation-id: vlan, network-type: vlan|vxlan,
518 physical-network: physnet-label}
garciadeblasebd66722019-01-31 16:01:31 +0000519 Returns a tuple with the network identifier and created_items, or raises an exception on error
520 created_items can be None or a dictionary where this method can include key-values that will be passed to
521 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
522 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
523 as not present.
524 """
tiernoae4a8d12016-07-08 12:30:39 +0200525 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasebd66722019-01-31 16:01:31 +0000526 # self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
kbsuba85c54d2019-10-17 16:30:32 +0000527
tierno7edb6752016-03-21 17:37:52 +0100528 try:
kbsuba85c54d2019-10-17 16:30:32 +0000529 vlan = None
530 if provider_network_profile:
531 vlan = provider_network_profile.get("segmentation-id")
garciadeblasedca7b32016-09-29 14:01:52 +0000532 new_net = None
garciadeblasebd66722019-01-31 16:01:31 +0000533 created_items = {}
tierno7edb6752016-03-21 17:37:52 +0100534 self._reload_connection()
535 network_dict = {'name': net_name, 'admin_state_up': True}
tierno6869ae72020-01-09 17:37:34 +0000536 if net_type in ("data", "ptp"):
537 provider_physical_network = None
538 if provider_network_profile and provider_network_profile.get("physical-network"):
539 provider_physical_network = provider_network_profile.get("physical-network")
540 # provider-network must be one of the dataplane_physcial_netowrk if this is a list. If it is string
541 # or not declared, just ignore the checking
542 if isinstance(self.config.get('dataplane_physical_net'), (tuple, list)) and \
543 provider_physical_network not in self.config['dataplane_physical_net']:
544 raise vimconn.vimconnConflictException(
545 "Invalid parameter 'provider-network:physical-network' for network creation. '{}' is not "
546 "one of the declared list at VIM_config:dataplane_physical_net".format(
547 provider_physical_network))
548 if not provider_physical_network: # use the default dataplane_physical_net
549 provider_physical_network = self.config.get('dataplane_physical_net')
550 # if it is non empty list, use the first value. If it is a string use the value directly
551 if isinstance(provider_physical_network, (tuple, list)) and provider_physical_network:
552 provider_physical_network = provider_physical_network[0]
553
554 if not provider_physical_network:
555 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at VIM_config "
556 "for creating underlay networks. or use the NS instantiation"
557 " parameter provider-network:physical-network for the VLD")
558
garciadeblasebd66722019-01-31 16:01:31 +0000559 if not self.config.get('multisegment_support'):
tierno6869ae72020-01-09 17:37:34 +0000560 network_dict["provider:physical_network"] = provider_physical_network
garciadeblas4af0d542020-02-18 16:01:13 +0100561 if provider_network_profile and "network-type" in provider_network_profile:
562 network_dict["provider:network_type"] = provider_network_profile["network-type"]
563 else:
564 network_dict["provider:network_type"] = self.config.get('dataplane_network_type','vlan')
tierno6869ae72020-01-09 17:37:34 +0000565 if vlan:
566 network_dict["provider:segmentation_id"] = vlan
garciadeblasebd66722019-01-31 16:01:31 +0000567 else:
tierno6869ae72020-01-09 17:37:34 +0000568 # Multi-segment case
garciadeblasebd66722019-01-31 16:01:31 +0000569 segment_list = []
tierno6869ae72020-01-09 17:37:34 +0000570 segment1_dict = {
571 "provider:physical_network": '',
572 "provider:network_type": 'vxlan'
573 }
garciadeblasebd66722019-01-31 16:01:31 +0000574 segment_list.append(segment1_dict)
tierno6869ae72020-01-09 17:37:34 +0000575 segment2_dict = {
576 "provider:physical_network": provider_physical_network,
577 "provider:network_type": "vlan"
578 }
579 if vlan:
580 segment2_dict["provider:segmentation_id"] = vlan
581 elif self.config.get('multisegment_vlan_range'):
garciadeblasebd66722019-01-31 16:01:31 +0000582 vlanID = self._generate_multisegment_vlanID()
583 segment2_dict["provider:segmentation_id"] = vlanID
584 # else
585 # raise vimconn.vimconnConflictException(
586 # "You must provide 'multisegment_vlan_range' at config dict before creating a multisegment network")
587 segment_list.append(segment2_dict)
588 network_dict["segments"] = segment_list
kate721d79b2017-06-24 04:21:38 -0700589
tierno6869ae72020-01-09 17:37:34 +0000590 # VIO Specific Changes. It needs a concrete VLAN
591 if self.vim_type == "VIO" and vlan is None:
592 if self.config.get('dataplane_net_vlan_range') is None:
593 raise vimconn.vimconnConflictException(
594 "You must provide 'dataplane_net_vlan_range' in format [start_ID - end_ID] at VIM_config "
595 "for creating underlay networks")
596 network_dict["provider:segmentation_id"] = self._generate_vlanID()
kate721d79b2017-06-24 04:21:38 -0700597
garciadeblasebd66722019-01-31 16:01:31 +0000598 network_dict["shared"] = shared
anwarsff168192019-05-06 11:23:07 +0530599 if self.config.get("disable_network_port_security"):
600 network_dict["port_security_enabled"] = False
garciadeblasebd66722019-01-31 16:01:31 +0000601 new_net = self.neutron.create_network({'network':network_dict})
602 # print new_net
603 # create subnetwork, even if there is no profile
garciadeblas9f8456e2016-09-05 05:02:59 +0200604 if not ip_profile:
605 ip_profile = {}
tierno41a69812018-02-16 14:34:33 +0100606 if not ip_profile.get('subnet_address'):
garciadeblas2299e3b2017-01-26 14:35:55 +0000607 #Fake subnet is required
608 subnet_rand = random.randint(0, 255)
609 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000610 if 'ip_version' not in ip_profile:
garciadeblas9f8456e2016-09-05 05:02:59 +0200611 ip_profile['ip_version'] = "IPv4"
garciadeblasebd66722019-01-31 16:01:31 +0000612 subnet = {"name": net_name+"-subnet",
tierno7edb6752016-03-21 17:37:52 +0100613 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200614 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
615 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100616 }
tiernoa1fb4462017-06-30 12:25:50 +0200617 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
tierno41a69812018-02-16 14:34:33 +0100618 if ip_profile.get('gateway_address'):
tierno55d234c2018-07-04 18:29:21 +0200619 subnet['gateway_ip'] = ip_profile['gateway_address']
620 else:
621 subnet['gateway_ip'] = None
garciadeblasedca7b32016-09-29 14:01:52 +0000622 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200623 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200624 if 'dhcp_enabled' in ip_profile:
tierno41a69812018-02-16 14:34:33 +0100625 subnet['enable_dhcp'] = False if \
626 ip_profile['dhcp_enabled']=="false" or ip_profile['dhcp_enabled']==False else True
627 if ip_profile.get('dhcp_start_address'):
tiernoa1fb4462017-06-30 12:25:50 +0200628 subnet['allocation_pools'] = []
garciadeblas9f8456e2016-09-05 05:02:59 +0200629 subnet['allocation_pools'].append(dict())
630 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
tierno41a69812018-02-16 14:34:33 +0100631 if ip_profile.get('dhcp_count'):
garciadeblas9f8456e2016-09-05 05:02:59 +0200632 #parts = ip_profile['dhcp_start_address'].split('.')
633 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
634 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200635 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200636 ip_str = str(netaddr.IPAddress(ip_int))
637 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000638 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100639 self.neutron.create_subnet({"subnet": subnet} )
garciadeblasebd66722019-01-31 16:01:31 +0000640
641 if net_type == "data" and self.config.get('multisegment_support'):
642 if self.config.get('l2gw_support'):
643 l2gw_list = self.neutron.list_l2_gateways().get("l2_gateways", ())
644 for l2gw in l2gw_list:
645 l2gw_conn = {}
646 l2gw_conn["l2_gateway_id"] = l2gw["id"]
647 l2gw_conn["network_id"] = new_net["network"]["id"]
648 l2gw_conn["segmentation_id"] = str(vlanID)
649 new_l2gw_conn = self.neutron.create_l2_gateway_connection({"l2_gateway_connection": l2gw_conn})
650 created_items["l2gwconn:" + str(new_l2gw_conn["l2_gateway_connection"]["id"])] = True
651 return new_net["network"]["id"], created_items
tierno41a69812018-02-16 14:34:33 +0100652 except Exception as e:
garciadeblasebd66722019-01-31 16:01:31 +0000653 #delete l2gw connections (if any) before deleting the network
654 for k, v in created_items.items():
655 if not v: # skip already deleted
656 continue
657 try:
658 k_item, _, k_id = k.partition(":")
659 if k_item == "l2gwconn":
660 self.neutron.delete_l2_gateway_connection(k_id)
661 except Exception as e2:
662 self.logger.error("Error deleting l2 gateway connection: {}: {}".format(type(e2).__name__, e2))
garciadeblasedca7b32016-09-29 14:01:52 +0000663 if new_net:
664 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200665 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100666
667 def get_network_list(self, filter_dict={}):
668 '''Obtain tenant networks of VIM
669 Filter_dict can be:
670 name: network name
671 id: network uuid
672 shared: boolean
673 tenant_id: tenant
674 admin_state_up: boolean
675 status: 'ACTIVE'
676 Returns the network list of dictionaries
677 '''
tiernoae4a8d12016-07-08 12:30:39 +0200678 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100679 try:
680 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +0100681 filter_dict_os = filter_dict.copy()
682 if self.api_version3 and "tenant_id" in filter_dict_os:
683 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id') #T ODO check
684 net_dict = self.neutron.list_networks(**filter_dict_os)
tierno00e3df72017-11-29 17:20:13 +0100685 net_list = net_dict["networks"]
tierno7edb6752016-03-21 17:37:52 +0100686 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200687 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000688 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200689 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100690
tiernoae4a8d12016-07-08 12:30:39 +0200691 def get_network(self, net_id):
692 '''Obtain details of network from VIM
693 Returns the network information from a network id'''
694 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100695 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200696 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100697 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200698 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100699 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200700 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100701 net = net_list[0]
702 subnets=[]
703 for subnet_id in net.get("subnets", () ):
704 try:
705 subnet = self.neutron.show_subnet(subnet_id)
706 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200707 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
708 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100709 subnets.append(subnet)
710 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100711 net["encapsulation"] = net.get('provider:network_type')
Anderson Bravalheri0fb70282018-12-16 19:28:37 +0000712 net["encapsulation_type"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100713 net["segmentation_id"] = net.get('provider:segmentation_id')
Anderson Bravalheri0fb70282018-12-16 19:28:37 +0000714 net["encapsulation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200715 return net
tierno7edb6752016-03-21 17:37:52 +0100716
garciadeblasebd66722019-01-31 16:01:31 +0000717 def delete_network(self, net_id, created_items=None):
718 """
719 Removes a tenant network from VIM and its associated elements
720 :param net_id: VIM identifier of the network, provided by method new_network
721 :param created_items: dictionary with extra items to be deleted. provided by method new_network
722 Returns the network identifier or raises an exception upon error or when network is not found
723 """
tiernoae4a8d12016-07-08 12:30:39 +0200724 self.logger.debug("Deleting network '%s' from VIM", net_id)
garciadeblasebd66722019-01-31 16:01:31 +0000725 if created_items == None:
726 created_items = {}
tierno7edb6752016-03-21 17:37:52 +0100727 try:
728 self._reload_connection()
garciadeblasebd66722019-01-31 16:01:31 +0000729 #delete l2gw connections (if any) before deleting the network
730 for k, v in created_items.items():
731 if not v: # skip already deleted
732 continue
733 try:
734 k_item, _, k_id = k.partition(":")
735 if k_item == "l2gwconn":
736 self.neutron.delete_l2_gateway_connection(k_id)
737 except Exception as e:
738 self.logger.error("Error deleting l2 gateway connection: {}: {}".format(type(e).__name__, e))
tierno7edb6752016-03-21 17:37:52 +0100739 #delete VM ports attached to this networks before the network
740 ports = self.neutron.list_ports(network_id=net_id)
741 for p in ports['ports']:
742 try:
743 self.neutron.delete_port(p["id"])
744 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200745 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100746 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200747 return net_id
748 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000749 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200750 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100751
tiernoae4a8d12016-07-08 12:30:39 +0200752 def refresh_nets_status(self, net_list):
753 '''Get the status of the networks
754 Params: the list of network identifiers
755 Returns a dictionary with:
756 net_id: #VIM id of this network
757 status: #Mandatory. Text with one of:
758 # DELETED (not found at vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100759 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
tiernoae4a8d12016-07-08 12:30:39 +0200760 # OTHER (Vim reported other status not understood)
761 # ERROR (VIM indicates an ERROR status)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100762 # ACTIVE, INACTIVE, DOWN (admin down),
tiernoae4a8d12016-07-08 12:30:39 +0200763 # BUILD (on building process)
764 #
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100765 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
tiernoae4a8d12016-07-08 12:30:39 +0200766 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
767
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000768 '''
tiernoae4a8d12016-07-08 12:30:39 +0200769 net_dict={}
770 for net_id in net_list:
771 net = {}
772 try:
773 net_vim = self.get_network(net_id)
774 if net_vim['status'] in netStatus2manoFormat:
775 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
776 else:
777 net["status"] = "OTHER"
778 net["error_msg"] = "VIM status reported " + net_vim['status']
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000779
tierno8e995ce2016-09-22 08:13:00 +0000780 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200781 net['status'] = 'DOWN'
Anderson Bravalheri0446cd52018-08-17 15:26:19 +0100782
783 net['vim_info'] = self.serialize(net_vim)
784
tiernoae4a8d12016-07-08 12:30:39 +0200785 if net_vim.get('fault'): #TODO
786 net['error_msg'] = str(net_vim['fault'])
787 except vimconn.vimconnNotFoundException as e:
788 self.logger.error("Exception getting net status: %s", str(e))
789 net['status'] = "DELETED"
790 net['error_msg'] = str(e)
791 except vimconn.vimconnException as e:
792 self.logger.error("Exception getting net status: %s", str(e))
793 net['status'] = "VIM_ERROR"
794 net['error_msg'] = str(e)
795 net_dict[net_id] = net
796 return net_dict
797
798 def get_flavor(self, flavor_id):
799 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
800 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100801 try:
802 self._reload_connection()
803 flavor = self.nova.flavors.find(id=flavor_id)
804 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200805 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000806 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200807 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100808
tiernocf157a82017-01-30 14:07:06 +0100809 def get_flavor_id_from_data(self, flavor_dict):
810 """Obtain flavor id that match the flavor description
811 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200812 flavor_dict: contains the required ram, vcpus, disk
813 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
814 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
815 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100816 """
tiernoe26fc7a2017-05-30 14:43:03 +0200817 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100818 try:
819 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200820 flavor_candidate_id = None
821 flavor_candidate_data = (10000, 10000, 10000)
822 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
823 # numa=None
anwarsae5f52c2019-04-22 10:35:27 +0530824 extended = flavor_dict.get("extended", {})
825 if extended:
tiernocf157a82017-01-30 14:07:06 +0100826 #TODO
tiernob7aa1bb2019-07-24 15:47:16 +0000827 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemented")
tiernocf157a82017-01-30 14:07:06 +0100828 # if len(numas) > 1:
829 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
830 # numa=numas[0]
831 # numas = extended.get("numas")
832 for flavor in self.nova.flavors.list():
833 epa = flavor.get_keys()
834 if epa:
835 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200836 # TODO
837 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
838 if flavor_data == flavor_target:
839 return flavor.id
840 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
841 flavor_candidate_id = flavor.id
842 flavor_candidate_data = flavor_data
843 if not exact_match and flavor_candidate_id:
844 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100845 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
846 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
847 self._format_exception(e)
848
anwarsae5f52c2019-04-22 10:35:27 +0530849 def process_resource_quota(self, quota, prefix, extra_specs):
850 """
851 :param prefix:
borsatti8a2dda32019-12-18 15:08:57 +0000852 :param extra_specs:
anwarsae5f52c2019-04-22 10:35:27 +0530853 :return:
854 """
855 if 'limit' in quota:
856 extra_specs["quota:" + prefix + "_limit"] = quota['limit']
857 if 'reserve' in quota:
858 extra_specs["quota:" + prefix + "_reservation"] = quota['reserve']
859 if 'shares' in quota:
860 extra_specs["quota:" + prefix + "_shares_level"] = "custom"
861 extra_specs["quota:" + prefix + "_shares_share"] = quota['shares']
862
tiernoae4a8d12016-07-08 12:30:39 +0200863 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100864 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200865 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 +0100866 Returns the flavor identifier
867 '''
tiernoae4a8d12016-07-08 12:30:39 +0200868 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100869 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200870 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100871 name_suffix = 0
anwarsc76a3ee2018-10-04 14:05:32 +0530872 try:
873 name=flavor_data['name']
874 while retry<max_retries:
875 retry+=1
876 try:
877 self._reload_connection()
878 if change_name_if_used:
879 #get used names
880 fl_names=[]
881 fl=self.nova.flavors.list()
882 for f in fl:
883 fl_names.append(f.name)
884 while name in fl_names:
885 name_suffix += 1
886 name = flavor_data['name']+"-" + str(name_suffix)
kate721d79b2017-06-24 04:21:38 -0700887
anwarsc76a3ee2018-10-04 14:05:32 +0530888 ram = flavor_data.get('ram',64)
889 vcpus = flavor_data.get('vcpus',1)
anwarsae5f52c2019-04-22 10:35:27 +0530890 extra_specs={}
tierno7edb6752016-03-21 17:37:52 +0100891
anwarsc76a3ee2018-10-04 14:05:32 +0530892 extended = flavor_data.get("extended")
893 if extended:
894 numas=extended.get("numas")
895 if numas:
896 numa_nodes = len(numas)
897 if numa_nodes > 1:
898 return -1, "Can not add flavor with more than one numa"
anwarsae5f52c2019-04-22 10:35:27 +0530899 extra_specs["hw:numa_nodes"] = str(numa_nodes)
900 extra_specs["hw:mem_page_size"] = "large"
901 extra_specs["hw:cpu_policy"] = "dedicated"
902 extra_specs["hw:numa_mempolicy"] = "strict"
anwarsc76a3ee2018-10-04 14:05:32 +0530903 if self.vim_type == "VIO":
anwarsae5f52c2019-04-22 10:35:27 +0530904 extra_specs["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
905 extra_specs["vmware:latency_sensitivity_level"] = "high"
anwarsc76a3ee2018-10-04 14:05:32 +0530906 for numa in numas:
907 #overwrite ram and vcpus
908 #check if key 'memory' is present in numa else use ram value at flavor
909 if 'memory' in numa:
910 ram = numa['memory']*1024
911 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
garciadeblasfa35a722019-04-11 19:15:49 +0200912 extra_specs["hw:cpu_sockets"] = 1
anwarsc76a3ee2018-10-04 14:05:32 +0530913 if 'paired-threads' in numa:
914 vcpus = numa['paired-threads']*2
915 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
anwarsae5f52c2019-04-22 10:35:27 +0530916 extra_specs["hw:cpu_thread_policy"] = "require"
917 extra_specs["hw:cpu_policy"] = "dedicated"
anwarsc76a3ee2018-10-04 14:05:32 +0530918 elif 'cores' in numa:
919 vcpus = numa['cores']
920 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
anwarsae5f52c2019-04-22 10:35:27 +0530921 extra_specs["hw:cpu_thread_policy"] = "isolate"
922 extra_specs["hw:cpu_policy"] = "dedicated"
anwarsc76a3ee2018-10-04 14:05:32 +0530923 elif 'threads' in numa:
924 vcpus = numa['threads']
925 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
anwarsae5f52c2019-04-22 10:35:27 +0530926 extra_specs["hw:cpu_thread_policy"] = "prefer"
927 extra_specs["hw:cpu_policy"] = "dedicated"
anwarsc76a3ee2018-10-04 14:05:32 +0530928 # for interface in numa.get("interfaces",() ):
929 # if interface["dedicated"]=="yes":
930 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
931 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
anwarsae5f52c2019-04-22 10:35:27 +0530932 elif extended.get("cpu-quota"):
933 self.process_resource_quota(extended.get("cpu-quota"), "cpu", extra_specs)
934 if extended.get("mem-quota"):
935 self.process_resource_quota(extended.get("mem-quota"), "memory", extra_specs)
936 if extended.get("vif-quota"):
937 self.process_resource_quota(extended.get("vif-quota"), "vif", extra_specs)
938 if extended.get("disk-io-quota"):
939 self.process_resource_quota(extended.get("disk-io-quota"), "disk_io", extra_specs)
anwarsc76a3ee2018-10-04 14:05:32 +0530940 #create flavor
941 new_flavor=self.nova.flavors.create(name,
942 ram,
943 vcpus,
944 flavor_data.get('disk',0),
945 is_public=flavor_data.get('is_public', True)
946 )
947 #add metadata
anwarsae5f52c2019-04-22 10:35:27 +0530948 if extra_specs:
949 new_flavor.set_keys(extra_specs)
anwarsc76a3ee2018-10-04 14:05:32 +0530950 return new_flavor.id
951 except nvExceptions.Conflict as e:
952 if change_name_if_used and retry < max_retries:
953 continue
954 self._format_exception(e)
955 #except nvExceptions.BadRequest as e:
956 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError, KeyError) as e:
957 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100958
tiernoae4a8d12016-07-08 12:30:39 +0200959 def delete_flavor(self,flavor_id):
960 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100961 '''
tiernoae4a8d12016-07-08 12:30:39 +0200962 try:
963 self._reload_connection()
964 self.nova.flavors.delete(flavor_id)
965 return flavor_id
966 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000967 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200968 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100969
tiernoae4a8d12016-07-08 12:30:39 +0200970 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100971 '''
tiernoae4a8d12016-07-08 12:30:39 +0200972 Adds a tenant image to VIM. imge_dict is a dictionary with:
973 name: name
974 disk_format: qcow2, vhd, vmdk, raw (by default), ...
975 location: path or URI
976 public: "yes" or "no"
977 metadata: metadata of the image
978 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100979 '''
tiernoae4a8d12016-07-08 12:30:39 +0200980 retry=0
981 max_retries=3
982 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100983 retry+=1
984 try:
985 self._reload_connection()
986 #determine format http://docs.openstack.org/developer/glance/formats.html
987 if "disk_format" in image_dict:
988 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100989 else: #autodiscover based on extension
tierno1beea862018-07-11 15:47:37 +0200990 if image_dict['location'].endswith(".qcow2"):
tierno7edb6752016-03-21 17:37:52 +0100991 disk_format="qcow2"
tierno1beea862018-07-11 15:47:37 +0200992 elif image_dict['location'].endswith(".vhd"):
tierno7edb6752016-03-21 17:37:52 +0100993 disk_format="vhd"
tierno1beea862018-07-11 15:47:37 +0200994 elif image_dict['location'].endswith(".vmdk"):
tierno7edb6752016-03-21 17:37:52 +0100995 disk_format="vmdk"
tierno1beea862018-07-11 15:47:37 +0200996 elif image_dict['location'].endswith(".vdi"):
tierno7edb6752016-03-21 17:37:52 +0100997 disk_format="vdi"
tierno1beea862018-07-11 15:47:37 +0200998 elif image_dict['location'].endswith(".iso"):
tierno7edb6752016-03-21 17:37:52 +0100999 disk_format="iso"
tierno1beea862018-07-11 15:47:37 +02001000 elif image_dict['location'].endswith(".aki"):
tierno7edb6752016-03-21 17:37:52 +01001001 disk_format="aki"
tierno1beea862018-07-11 15:47:37 +02001002 elif image_dict['location'].endswith(".ari"):
tierno7edb6752016-03-21 17:37:52 +01001003 disk_format="ari"
tierno1beea862018-07-11 15:47:37 +02001004 elif image_dict['location'].endswith(".ami"):
tierno7edb6752016-03-21 17:37:52 +01001005 disk_format="ami"
1006 else:
1007 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +02001008 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
shashankjain3c83a212018-10-04 13:05:46 +05301009 if self.vim_type == "VIO":
1010 container_format = "bare"
1011 if 'container_format' in image_dict:
1012 container_format = image_dict['container_format']
1013 new_image = self.glance.images.create(name=image_dict['name'], container_format=container_format,
1014 disk_format=disk_format)
1015 else:
1016 new_image = self.glance.images.create(name=image_dict['name'])
tierno1beea862018-07-11 15:47:37 +02001017 if image_dict['location'].startswith("http"):
1018 # TODO there is not a method to direct download. It must be downloaded locally with requests
1019 raise vimconn.vimconnNotImplemented("Cannot create image from URL")
tierno7edb6752016-03-21 17:37:52 +01001020 else: #local path
1021 with open(image_dict['location']) as fimage:
tierno1beea862018-07-11 15:47:37 +02001022 self.glance.images.upload(new_image.id, fimage)
1023 #new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
1024 # container_format="bare", data=fimage, disk_format=disk_format)
tierno7edb6752016-03-21 17:37:52 +01001025 metadata_to_load = image_dict.get('metadata')
shashankjain3c83a212018-10-04 13:05:46 +05301026 # TODO location is a reserved word for current openstack versions. fixed for VIO please check for openstack
1027 if self.vim_type == "VIO":
1028 metadata_to_load['upload_location'] = image_dict['location']
1029 else:
1030 metadata_to_load['location'] = image_dict['location']
tierno1beea862018-07-11 15:47:37 +02001031 self.glance.images.update(new_image.id, **metadata_to_load)
tiernoae4a8d12016-07-08 12:30:39 +02001032 return new_image.id
1033 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
1034 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +00001035 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001036 if retry==max_retries:
1037 continue
1038 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001039 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +02001040 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
1041 http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001042
tiernoae4a8d12016-07-08 12:30:39 +02001043 def delete_image(self, image_id):
1044 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +01001045 '''
tiernoae4a8d12016-07-08 12:30:39 +02001046 try:
1047 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +02001048 self.glance.images.delete(image_id)
tiernoae4a8d12016-07-08 12:30:39 +02001049 return image_id
shashankjain3c83a212018-10-04 13:05:46 +05301050 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, gl1Exceptions.HTTPNotFound, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +02001051 self._format_exception(e)
1052
1053 def get_image_id_from_path(self, path):
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001054 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +02001055 try:
1056 self._reload_connection()
tierno1beea862018-07-11 15:47:37 +02001057 images = self.glance.images.list()
tiernoae4a8d12016-07-08 12:30:39 +02001058 for image in images:
1059 if image.metadata.get("location")==path:
1060 return image.id
1061 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +00001062 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001063 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001064
garciadeblasb69fa9f2016-09-28 12:04:10 +02001065 def get_image_list(self, filter_dict={}):
1066 '''Obtain tenant images from VIM
1067 Filter_dict can be:
1068 id: image id
1069 name: image name
1070 checksum: image checksum
1071 Returns the image list of dictionaries:
1072 [{<the fields at Filter_dict plus some VIM specific>}, ...]
1073 List can be empty
1074 '''
1075 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
1076 try:
1077 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01001078 filter_dict_os = filter_dict.copy()
garciadeblasb69fa9f2016-09-28 12:04:10 +02001079 #First we filter by the available filter fields: name, id. The others are removed.
tierno1beea862018-07-11 15:47:37 +02001080 image_list = self.glance.images.list()
garciadeblasb69fa9f2016-09-28 12:04:10 +02001081 filtered_list = []
1082 for image in image_list:
tierno3cb8dc32017-10-24 18:13:19 +02001083 try:
tierno1beea862018-07-11 15:47:37 +02001084 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
1085 continue
1086 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
1087 continue
1088 if filter_dict.get("checksum") and image["checksum"] != filter_dict["checksum"]:
1089 continue
1090
1091 filtered_list.append(image.copy())
tierno3cb8dc32017-10-24 18:13:19 +02001092 except gl1Exceptions.HTTPNotFound:
1093 pass
garciadeblasb69fa9f2016-09-28 12:04:10 +02001094 return filtered_list
1095 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
1096 self._format_exception(e)
1097
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001098 def __wait_for_vm(self, vm_id, status):
1099 """wait until vm is in the desired status and return True.
1100 If the VM gets in ERROR status, return false.
1101 If the timeout is reached generate an exception"""
1102 elapsed_time = 0
1103 while elapsed_time < server_timeout:
1104 vm_status = self.nova.servers.get(vm_id).status
1105 if vm_status == status:
1106 return True
1107 if vm_status == 'ERROR':
1108 return False
tierno1df468d2018-07-06 14:25:16 +02001109 time.sleep(5)
1110 elapsed_time += 5
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001111
1112 # if we exceeded the timeout rollback
1113 if elapsed_time >= server_timeout:
1114 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
1115 http_code=vimconn.HTTP_Request_Timeout)
1116
mirabal29356312017-07-27 12:21:22 +02001117 def _get_openstack_availablity_zones(self):
1118 """
1119 Get from openstack availability zones available
1120 :return:
1121 """
1122 try:
1123 openstack_availability_zone = self.nova.availability_zones.list()
1124 openstack_availability_zone = [str(zone.zoneName) for zone in openstack_availability_zone
1125 if zone.zoneName != 'internal']
1126 return openstack_availability_zone
1127 except Exception as e:
1128 return None
1129
1130 def _set_availablity_zones(self):
1131 """
1132 Set vim availablity zone
1133 :return:
1134 """
1135
1136 if 'availability_zone' in self.config:
1137 vim_availability_zones = self.config.get('availability_zone')
1138 if isinstance(vim_availability_zones, str):
1139 self.availability_zone = [vim_availability_zones]
1140 elif isinstance(vim_availability_zones, list):
1141 self.availability_zone = vim_availability_zones
1142 else:
1143 self.availability_zone = self._get_openstack_availablity_zones()
1144
tierno5a3273c2017-08-29 11:43:46 +02001145 def _get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
mirabal29356312017-07-27 12:21:22 +02001146 """
tierno5a3273c2017-08-29 11:43:46 +02001147 Return thge availability zone to be used by the created VM.
1148 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +02001149 """
tierno5a3273c2017-08-29 11:43:46 +02001150 if availability_zone_index is None:
1151 if not self.config.get('availability_zone'):
1152 return None
1153 elif isinstance(self.config.get('availability_zone'), str):
1154 return self.config['availability_zone']
1155 else:
1156 # TODO consider using a different parameter at config for default AV and AV list match
1157 return self.config['availability_zone'][0]
mirabal29356312017-07-27 12:21:22 +02001158
tierno5a3273c2017-08-29 11:43:46 +02001159 vim_availability_zones = self.availability_zone
1160 # check if VIM offer enough availability zones describe in the VNFD
1161 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
1162 # check if all the names of NFV AV match VIM AV names
1163 match_by_index = False
1164 for av in availability_zone_list:
1165 if av not in vim_availability_zones:
1166 match_by_index = True
1167 break
1168 if match_by_index:
1169 return vim_availability_zones[availability_zone_index]
1170 else:
1171 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +02001172 else:
tierno5a3273c2017-08-29 11:43:46 +02001173 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
mirabal29356312017-07-27 12:21:22 +02001174
tierno5a3273c2017-08-29 11:43:46 +02001175 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
1176 availability_zone_index=None, availability_zone_list=None):
tierno98e909c2017-10-14 13:27:03 +02001177 """Adds a VM instance to VIM
tierno7edb6752016-03-21 17:37:52 +01001178 Params:
1179 start: indicates if VM must start or boot in pause mode. Ignored
1180 image_id,flavor_id: iamge and flavor uuid
1181 net_list: list of interfaces, each one is a dictionary with:
1182 name:
1183 net_id: network uuid to connect
1184 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
1185 model: interface model, ignored #TODO
1186 mac_address: used for SR-IOV ifaces #TODO for other types
1187 use: 'data', 'bridge', 'mgmt'
tierno66eba6e2017-11-10 17:09:18 +01001188 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
tierno7edb6752016-03-21 17:37:52 +01001189 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +05001190 floating_ip: True/False (or it can be None)
tierno41a69812018-02-16 14:34:33 +01001191 'cloud_config': (optional) dictionary with:
1192 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
1193 'users': (optional) list of users to be inserted, each item is a dict with:
1194 'name': (mandatory) user name,
1195 'key-pairs': (optional) list of strings with the public key to be inserted to the user
1196 'user-data': (optional) string is a text script to be passed directly to cloud-init
1197 'config-files': (optional). List of files to be transferred. Each item is a dict with:
1198 'dest': (mandatory) string with the destination absolute path
1199 'encoding': (optional, by default text). Can be one of:
1200 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
1201 'content' (mandatory): string with the content of the file
1202 'permissions': (optional) string with file permissions, typically octal notation '0644'
1203 'owner': (optional) file owner, string with the format 'owner:group'
1204 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
mirabal29356312017-07-27 12:21:22 +02001205 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
1206 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
1207 'size': (mandatory) string with the size of the disk in GB
tierno1df468d2018-07-06 14:25:16 +02001208 'vim_id' (optional) should use this existing volume id
tierno5a3273c2017-08-29 11:43:46 +02001209 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
1210 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
1211 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +01001212 #TODO ip, security groups
tierno98e909c2017-10-14 13:27:03 +02001213 Returns a tuple with the instance identifier and created_items or raises an exception on error
1214 created_items can be None or a dictionary where this method can include key-values that will be passed to
1215 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
1216 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
1217 as not present.
1218 """
tiernofa51c202017-01-27 14:58:17 +01001219 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 +01001220 try:
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001221 server = None
tierno98e909c2017-10-14 13:27:03 +02001222 created_items = {}
tiernob0b9dab2017-10-14 14:25:20 +02001223 # metadata = {}
tierno98e909c2017-10-14 13:27:03 +02001224 net_list_vim = []
1225 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 +02001226 no_secured_ports = [] # List of port-is with port-security disabled
tierno7edb6752016-03-21 17:37:52 +01001227 self._reload_connection()
tiernob0b9dab2017-10-14 14:25:20 +02001228 # metadata_vpci = {} # For a specific neutron plugin
tiernob84cbdc2017-07-07 14:30:30 +02001229 block_device_mapping = None
tiernoa05b65a2019-02-01 12:30:27 +00001230
tierno7edb6752016-03-21 17:37:52 +01001231 for net in net_list:
tierno98e909c2017-10-14 13:27:03 +02001232 if not net.get("net_id"): # skip non connected iface
tierno7edb6752016-03-21 17:37:52 +01001233 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001234
tiernoa05b65a2019-02-01 12:30:27 +00001235 port_dict = {
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001236 "network_id": net["net_id"],
1237 "name": net.get("name"),
1238 "admin_state_up": True
1239 }
tiernoa05b65a2019-02-01 12:30:27 +00001240 if self.config.get("security_groups") and net.get("port_security") is not False and \
1241 not self.config.get("no_port_security_extension"):
1242 if not self.security_groups_id:
1243 self._get_ids_from_name()
1244 port_dict["security_groups"] = self.security_groups_id
1245
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001246 if net["type"]=="virtual":
tiernob0b9dab2017-10-14 14:25:20 +02001247 pass
1248 # if "vpci" in net:
1249 # metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
tierno66eba6e2017-11-10 17:09:18 +01001250 elif net["type"] == "VF" or net["type"] == "SR-IOV": # for VF
tiernob0b9dab2017-10-14 14:25:20 +02001251 # if "vpci" in net:
1252 # if "VF" not in metadata_vpci:
1253 # metadata_vpci["VF"]=[]
1254 # metadata_vpci["VF"].append([ net["vpci"], "" ])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001255 port_dict["binding:vnic_type"]="direct"
tiernob0b9dab2017-10-14 14:25:20 +02001256 # VIO specific Changes
kate721d79b2017-06-24 04:21:38 -07001257 if self.vim_type == "VIO":
tiernob0b9dab2017-10-14 14:25:20 +02001258 # Need to create port with port_security_enabled = False and no-security-groups
kate721d79b2017-06-24 04:21:38 -07001259 port_dict["port_security_enabled"]=False
1260 port_dict["provider_security_groups"]=[]
1261 port_dict["security_groups"]=[]
tierno66eba6e2017-11-10 17:09:18 +01001262 else: # For PT PCI-PASSTHROUGH
tiernob0b9dab2017-10-14 14:25:20 +02001263 # VIO specific Changes
1264 # Current VIO release does not support port with type 'direct-physical'
1265 # So no need to create virtual port in case of PCI-device.
1266 # Will update port_dict code when support gets added in next VIO release
kate721d79b2017-06-24 04:21:38 -07001267 if self.vim_type == "VIO":
tiernob0b9dab2017-10-14 14:25:20 +02001268 raise vimconn.vimconnNotSupportedException(
1269 "Current VIO release does not support full passthrough (PT)")
1270 # if "vpci" in net:
1271 # if "PF" not in metadata_vpci:
1272 # metadata_vpci["PF"]=[]
1273 # metadata_vpci["PF"].append([ net["vpci"], "" ])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001274 port_dict["binding:vnic_type"]="direct-physical"
1275 if not port_dict["name"]:
1276 port_dict["name"]=name
1277 if net.get("mac_address"):
1278 port_dict["mac_address"]=net["mac_address"]
tierno41a69812018-02-16 14:34:33 +01001279 if net.get("ip_address"):
1280 port_dict["fixed_ips"] = [{'ip_address': net["ip_address"]}]
1281 # TODO add 'subnet_id': <subnet_id>
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001282 new_port = self.neutron.create_port({"port": port_dict })
tierno00e3df72017-11-29 17:20:13 +01001283 created_items["port:" + str(new_port["port"]["id"])] = True
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001284 net["mac_adress"] = new_port["port"]["mac_address"]
1285 net["vim_id"] = new_port["port"]["id"]
tiernob84cbdc2017-07-07 14:30:30 +02001286 # if try to use a network without subnetwork, it will return a emtpy list
1287 fixed_ips = new_port["port"].get("fixed_ips")
1288 if fixed_ips:
1289 net["ip"] = fixed_ips[0].get("ip_address")
1290 else:
1291 net["ip"] = None
montesmoreno994a29d2017-08-22 11:23:06 +02001292
1293 port = {"port-id": new_port["port"]["id"]}
1294 if float(self.nova.api_version.get_string()) >= 2.32:
1295 port["tag"] = new_port["port"]["name"]
1296 net_list_vim.append(port)
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001297
ahmadsaf853d452016-12-22 11:33:47 +05001298 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +01001299 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +05001300 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +01001301 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
1302 net['exit_on_floating_ip_error'] = False
1303 external_network.append(net)
tierno326fd5e2018-02-22 11:58:59 +01001304 net['floating_ip'] = self.config.get('use_floating_ip')
tiernof8383b82017-01-18 15:49:48 +01001305
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001306 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1307 # As a workaround we wait until the VM is active and then disable the port-security
tierno4d1ce222018-04-06 10:41:06 +02001308 if net.get("port_security") == False and not self.config.get("no_port_security_extension"):
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001309 no_secured_ports.append(new_port["port"]["id"])
1310
tiernob0b9dab2017-10-14 14:25:20 +02001311 # if metadata_vpci:
1312 # metadata = {"pci_assignement": json.dumps(metadata_vpci)}
1313 # if len(metadata["pci_assignement"]) >255:
1314 # #limit the metadata size
1315 # #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1316 # self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
1317 # metadata = {}
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001318
tiernob0b9dab2017-10-14 14:25:20 +02001319 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s'",
1320 name, image_id, flavor_id, str(net_list_vim), description)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001321
tierno98e909c2017-10-14 13:27:03 +02001322 # cloud config
tierno0a1437e2017-10-02 00:17:43 +02001323 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00001324
tierno98e909c2017-10-14 13:27:03 +02001325 # Create additional volumes in case these are present in disk_list
montesmoreno0c8def02016-12-22 12:16:23 +00001326 base_disk_index = ord('b')
tierno1df468d2018-07-06 14:25:16 +02001327 if disk_list:
tiernob84cbdc2017-07-07 14:30:30 +02001328 block_device_mapping = {}
montesmoreno0c8def02016-12-22 12:16:23 +00001329 for disk in disk_list:
tierno1df468d2018-07-06 14:25:16 +02001330 if disk.get('vim_id'):
1331 block_device_mapping['_vd' + chr(base_disk_index)] = disk['vim_id']
montesmoreno0c8def02016-12-22 12:16:23 +00001332 else:
tierno1df468d2018-07-06 14:25:16 +02001333 if 'image_id' in disk:
1334 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1335 chr(base_disk_index), imageRef=disk['image_id'])
1336 else:
1337 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1338 chr(base_disk_index))
1339 created_items["volume:" + str(volume.id)] = True
1340 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
montesmoreno0c8def02016-12-22 12:16:23 +00001341 base_disk_index += 1
1342
tierno1df468d2018-07-06 14:25:16 +02001343 # Wait until created volumes are with status available
montesmoreno0c8def02016-12-22 12:16:23 +00001344 elapsed_time = 0
tierno1df468d2018-07-06 14:25:16 +02001345 while elapsed_time < volume_timeout:
1346 for created_item in created_items:
1347 v, _, volume_id = created_item.partition(":")
1348 if v == 'volume':
1349 if self.cinder.volumes.get(volume_id).status != 'available':
1350 break
1351 else: # all ready: break from while
1352 break
1353 time.sleep(5)
1354 elapsed_time += 5
tiernob0b9dab2017-10-14 14:25:20 +02001355 # If we exceeded the timeout rollback
montesmoreno0c8def02016-12-22 12:16:23 +00001356 if elapsed_time >= volume_timeout:
montesmoreno0c8def02016-12-22 12:16:23 +00001357 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
1358 http_code=vimconn.HTTP_Request_Timeout)
mirabal29356312017-07-27 12:21:22 +02001359 # get availability Zone
tierno5a3273c2017-08-29 11:43:46 +02001360 vm_av_zone = self._get_vm_availability_zone(availability_zone_index, availability_zone_list)
montesmoreno0c8def02016-12-22 12:16:23 +00001361
tiernob0b9dab2017-10-14 14:25:20 +02001362 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
mirabal29356312017-07-27 12:21:22 +02001363 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
tiernob0b9dab2017-10-14 14:25:20 +02001364 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
tiernoa05b65a2019-02-01 12:30:27 +00001365 self.config.get("security_groups"), vm_av_zone,
1366 self.config.get('keypair'), userdata, config_drive,
1367 block_device_mapping))
tiernob0b9dab2017-10-14 14:25:20 +02001368 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim,
tiernoa05b65a2019-02-01 12:30:27 +00001369 security_groups=self.config.get("security_groups"),
1370 # TODO remove security_groups in future versions. Already at neutron port
mirabal29356312017-07-27 12:21:22 +02001371 availability_zone=vm_av_zone,
montesmoreno0c8def02016-12-22 12:16:23 +00001372 key_name=self.config.get('keypair'),
1373 userdata=userdata,
tiernob84cbdc2017-07-07 14:30:30 +02001374 config_drive=config_drive,
1375 block_device_mapping=block_device_mapping
montesmoreno0c8def02016-12-22 12:16:23 +00001376 ) # , description=description)
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001377
tierno326fd5e2018-02-22 11:58:59 +01001378 vm_start_time = time.time()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001379 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1380 if no_secured_ports:
1381 self.__wait_for_vm(server.id, 'ACTIVE')
1382
1383 for port_id in no_secured_ports:
1384 try:
tierno4d1ce222018-04-06 10:41:06 +02001385 self.neutron.update_port(port_id,
1386 {"port": {"port_security_enabled": False, "security_groups": None}})
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001387 except Exception as e:
tierno4d1ce222018-04-06 10:41:06 +02001388 raise vimconn.vimconnException("It was not possible to disable port security for port {}".format(
1389 port_id))
tierno98e909c2017-10-14 13:27:03 +02001390 # print "DONE :-)", server
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001391
tierno4d1ce222018-04-06 10:41:06 +02001392 # pool_id = None
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001393 if external_network:
tierno98e909c2017-10-14 13:27:03 +02001394 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
ahmadsaf853d452016-12-22 11:33:47 +05001395 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +01001396 try:
tiernof8383b82017-01-18 15:49:48 +01001397 assigned = False
tierno98e909c2017-10-14 13:27:03 +02001398 while not assigned:
tiernof8383b82017-01-18 15:49:48 +01001399 if floating_ips:
1400 ip = floating_ips.pop(0)
tierno326fd5e2018-02-22 11:58:59 +01001401 if ip.get("port_id", False) or ip.get('tenant_id') != server.tenant_id:
1402 continue
1403 if isinstance(floating_network['floating_ip'], str):
1404 if ip.get("floating_network_id") != floating_network['floating_ip']:
1405 continue
tierno7d782ef2019-10-04 12:56:31 +00001406 free_floating_ip = ip["id"]
tiernof8383b82017-01-18 15:49:48 +01001407 else:
tiernocb3cca22018-05-31 15:08:52 +02001408 if isinstance(floating_network['floating_ip'], str) and \
1409 floating_network['floating_ip'].lower() != "true":
tierno326fd5e2018-02-22 11:58:59 +01001410 pool_id = floating_network['floating_ip']
1411 else:
tierno4d1ce222018-04-06 10:41:06 +02001412 # Find the external network
tierno326fd5e2018-02-22 11:58:59 +01001413 external_nets = list()
1414 for net in self.neutron.list_networks()['networks']:
1415 if net['router:external']:
1416 external_nets.append(net)
tiernof8383b82017-01-18 15:49:48 +01001417
tierno326fd5e2018-02-22 11:58:59 +01001418 if len(external_nets) == 0:
1419 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
1420 "network is present",
1421 http_code=vimconn.HTTP_Conflict)
1422 if len(external_nets) > 1:
1423 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
1424 "external networks are present",
1425 http_code=vimconn.HTTP_Conflict)
tiernof8383b82017-01-18 15:49:48 +01001426
tierno326fd5e2018-02-22 11:58:59 +01001427 pool_id = external_nets[0].get('id')
tiernof8383b82017-01-18 15:49:48 +01001428 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +05001429 try:
tierno4d1ce222018-04-06 10:41:06 +02001430 # self.logger.debug("Creating floating IP")
tiernof8383b82017-01-18 15:49:48 +01001431 new_floating_ip = self.neutron.create_floatingip(param)
tierno7d782ef2019-10-04 12:56:31 +00001432 free_floating_ip = new_floating_ip['floatingip']['id']
ahmadsaf853d452016-12-22 11:33:47 +05001433 except Exception as e:
tierno326fd5e2018-02-22 11:58:59 +01001434 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create new floating_ip " +
1435 str(e), http_code=vimconn.HTTP_Conflict)
1436
tierno326fd5e2018-02-22 11:58:59 +01001437 while not assigned:
1438 try:
tierno7d782ef2019-10-04 12:56:31 +00001439 # the vim_id key contains the neutron.port_id
1440 self.neutron.update_floatingip(free_floating_ip,
1441 {"floatingip": {"port_id": floating_network["vim_id"]}})
1442 # Using nove is deprecated on nova client 10.0
tierno326fd5e2018-02-22 11:58:59 +01001443 assigned = True
1444 except Exception as e:
tierno4d1ce222018-04-06 10:41:06 +02001445 # openstack need some time after VM creation to asign an IP. So retry if fails
tierno326fd5e2018-02-22 11:58:59 +01001446 vm_status = self.nova.servers.get(server.id).status
1447 if vm_status != 'ACTIVE' and vm_status != 'ERROR':
1448 if time.time() - vm_start_time < server_timeout:
1449 time.sleep(5)
1450 continue
tierno4d1ce222018-04-06 10:41:06 +02001451 raise vimconn.vimconnException(
1452 "Cannot create floating_ip: {} {}".format(type(e).__name__, e),
1453 http_code=vimconn.HTTP_Conflict)
tierno326fd5e2018-02-22 11:58:59 +01001454
tiernof8383b82017-01-18 15:49:48 +01001455 except Exception as e:
1456 if not floating_network['exit_on_floating_ip_error']:
tierno7d782ef2019-10-04 12:56:31 +00001457 self.logger.warning("Cannot create floating_ip. %s", str(e))
tiernof8383b82017-01-18 15:49:48 +01001458 continue
tiernof8383b82017-01-18 15:49:48 +01001459 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001460
tierno98e909c2017-10-14 13:27:03 +02001461 return server.id, created_items
tierno7edb6752016-03-21 17:37:52 +01001462# except nvExceptions.NotFound as e:
1463# error_value=-vimconn.HTTP_Not_Found
1464# error_text= "vm instance %s not found" % vm_id
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001465# except TypeError as e:
1466# raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1467
1468 except Exception as e:
tierno98e909c2017-10-14 13:27:03 +02001469 server_id = None
1470 if server:
1471 server_id = server.id
1472 try:
1473 self.delete_vminstance(server_id, created_items)
1474 except Exception as e2:
1475 self.logger.error("new_vminstance rollback fail {}".format(e2))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001476
tiernoae4a8d12016-07-08 12:30:39 +02001477 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001478
tiernoae4a8d12016-07-08 12:30:39 +02001479 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +01001480 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001481 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +01001482 try:
1483 self._reload_connection()
1484 server = self.nova.servers.find(id=vm_id)
1485 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +02001486 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +00001487 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001488 self._format_exception(e)
1489
1490 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +01001491 '''
1492 Get a console for the virtual machine
1493 Params:
1494 vm_id: uuid of the VM
1495 console_type, can be:
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001496 "novnc" (by default), "xvpvnc" for VNC types,
tierno7edb6752016-03-21 17:37:52 +01001497 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001498 Returns dict with the console parameters:
1499 protocol: ssh, ftp, http, https, ...
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001500 server: usually ip address
1501 port: the http, ssh, ... port
1502 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001503 '''
tiernoae4a8d12016-07-08 12:30:39 +02001504 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001505 try:
1506 self._reload_connection()
1507 server = self.nova.servers.find(id=vm_id)
1508 if console_type == None or console_type == "novnc":
1509 console_dict = server.get_vnc_console("novnc")
1510 elif console_type == "xvpvnc":
1511 console_dict = server.get_vnc_console(console_type)
1512 elif console_type == "rdp-html5":
1513 console_dict = server.get_rdp_console(console_type)
1514 elif console_type == "spice-html5":
1515 console_dict = server.get_spice_console(console_type)
1516 else:
tiernoae4a8d12016-07-08 12:30:39 +02001517 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001518
tierno7edb6752016-03-21 17:37:52 +01001519 console_dict1 = console_dict.get("console")
1520 if console_dict1:
1521 console_url = console_dict1.get("url")
1522 if console_url:
1523 #parse console_url
1524 protocol_index = console_url.find("//")
1525 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1526 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1527 if protocol_index < 0 or port_index<0 or suffix_index<0:
1528 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1529 console_dict={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001530 "server": console_url[protocol_index+2:port_index],
1531 "port": console_url[port_index:suffix_index],
1532 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001533 }
1534 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001535 return console_dict
1536 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001537
tierno8e995ce2016-09-22 08:13:00 +00001538 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001539 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001540
tierno98e909c2017-10-14 13:27:03 +02001541 def delete_vminstance(self, vm_id, created_items=None):
tiernoae4a8d12016-07-08 12:30:39 +02001542 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001543 '''
tiernoae4a8d12016-07-08 12:30:39 +02001544 #print "osconnector: Getting VM from VIM"
tierno98e909c2017-10-14 13:27:03 +02001545 if created_items == None:
1546 created_items = {}
tierno7edb6752016-03-21 17:37:52 +01001547 try:
1548 self._reload_connection()
tierno98e909c2017-10-14 13:27:03 +02001549 # delete VM ports attached to this networks before the virtual machine
1550 for k, v in created_items.items():
1551 if not v: # skip already deleted
1552 continue
tierno7edb6752016-03-21 17:37:52 +01001553 try:
tiernoad6bdd42018-01-10 10:43:46 +01001554 k_item, _, k_id = k.partition(":")
1555 if k_item == "port":
1556 self.neutron.delete_port(k_id)
tierno7edb6752016-03-21 17:37:52 +01001557 except Exception as e:
tierno00e3df72017-11-29 17:20:13 +01001558 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
montesmoreno0c8def02016-12-22 12:16:23 +00001559
tierno98e909c2017-10-14 13:27:03 +02001560 # #commented because detaching the volumes makes the servers.delete not work properly ?!?
1561 # #dettach volumes attached
1562 # server = self.nova.servers.get(vm_id)
1563 # volumes_attached_dict = server._info['os-extended-volumes:volumes_attached'] #volume['id']
1564 # #for volume in volumes_attached_dict:
1565 # # self.cinder.volumes.detach(volume['id'])
montesmoreno0c8def02016-12-22 12:16:23 +00001566
tierno98e909c2017-10-14 13:27:03 +02001567 if vm_id:
1568 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001569
tierno98e909c2017-10-14 13:27:03 +02001570 # delete volumes. Although having detached, they should have in active status before deleting
1571 # we ensure in this loop
montesmoreno0c8def02016-12-22 12:16:23 +00001572 keep_waiting = True
1573 elapsed_time = 0
1574 while keep_waiting and elapsed_time < volume_timeout:
1575 keep_waiting = False
tierno98e909c2017-10-14 13:27:03 +02001576 for k, v in created_items.items():
1577 if not v: # skip already deleted
1578 continue
1579 try:
tiernoad6bdd42018-01-10 10:43:46 +01001580 k_item, _, k_id = k.partition(":")
1581 if k_item == "volume":
1582 if self.cinder.volumes.get(k_id).status != 'available':
tierno98e909c2017-10-14 13:27:03 +02001583 keep_waiting = True
1584 else:
tiernoad6bdd42018-01-10 10:43:46 +01001585 self.cinder.volumes.delete(k_id)
tierno98e909c2017-10-14 13:27:03 +02001586 except Exception as e:
tierno00e3df72017-11-29 17:20:13 +01001587 self.logger.error("Error deleting volume: {}: {}".format(type(e).__name__, e))
montesmoreno0c8def02016-12-22 12:16:23 +00001588 if keep_waiting:
1589 time.sleep(1)
1590 elapsed_time += 1
tierno98e909c2017-10-14 13:27:03 +02001591 return None
tierno8e995ce2016-09-22 08:13:00 +00001592 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001593 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001594
tiernoae4a8d12016-07-08 12:30:39 +02001595 def refresh_vms_status(self, vm_list):
1596 '''Get the status of the virtual machines and their interfaces/ports
1597 Params: the list of VM identifiers
1598 Returns a dictionary with:
1599 vm_id: #VIM id of this Virtual Machine
1600 status: #Mandatory. Text with one of:
1601 # DELETED (not found at vim)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001602 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
tiernoae4a8d12016-07-08 12:30:39 +02001603 # OTHER (Vim reported other status not understood)
1604 # ERROR (VIM indicates an ERROR status)
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001605 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
tiernoae4a8d12016-07-08 12:30:39 +02001606 # CREATING (on building process), ERROR
1607 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1608 #
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001609 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
tiernoae4a8d12016-07-08 12:30:39 +02001610 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1611 interfaces:
1612 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1613 mac_address: #Text format XX:XX:XX:XX:XX:XX
1614 vim_net_id: #network id where this interface is connected
1615 vim_interface_id: #interface/port VIM id
1616 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001617 compute_node: #identification of compute node where PF,VF interface is allocated
1618 pci: #PCI address of the NIC that hosts the PF,VF
1619 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001620 '''
tiernoae4a8d12016-07-08 12:30:39 +02001621 vm_dict={}
1622 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1623 for vm_id in vm_list:
1624 vm={}
1625 try:
1626 vm_vim = self.get_vminstance(vm_id)
1627 if vm_vim['status'] in vmStatus2manoFormat:
1628 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001629 else:
tiernoae4a8d12016-07-08 12:30:39 +02001630 vm['status'] = "OTHER"
1631 vm['error_msg'] = "VIM status reported " + vm_vim['status']
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001632
1633 vm['vim_info'] = self.serialize(vm_vim)
1634
tiernoae4a8d12016-07-08 12:30:39 +02001635 vm["interfaces"] = []
1636 if vm_vim.get('fault'):
1637 vm['error_msg'] = str(vm_vim['fault'])
1638 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001639 try:
tiernoae4a8d12016-07-08 12:30:39 +02001640 self._reload_connection()
tiernob42fd9b2018-06-20 10:44:32 +02001641 port_dict = self.neutron.list_ports(device_id=vm_id)
tiernoae4a8d12016-07-08 12:30:39 +02001642 for port in port_dict["ports"]:
1643 interface={}
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001644 interface['vim_info'] = self.serialize(port)
tiernoae4a8d12016-07-08 12:30:39 +02001645 interface["mac_address"] = port.get("mac_address")
1646 interface["vim_net_id"] = port["network_id"]
1647 interface["vim_interface_id"] = port["id"]
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001648 # check if OS-EXT-SRV-ATTR:host is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04001649 # in case of non-admin credentials, it will be missing
1650 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1651 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001652 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001653
Anderson Bravalheri0446cd52018-08-17 15:26:19 +01001654 # check if binding:profile is there,
Mike Marchetti5b9da422017-05-02 15:35:47 -04001655 # in case of non-admin credentials, it will be missing
1656 if port.get('binding:profile'):
1657 if port['binding:profile'].get('pci_slot'):
1658 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1659 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1660 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1661 pci = port['binding:profile']['pci_slot']
1662 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1663 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001664 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001665 #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 +01001666 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001667 if network['network'].get('provider:network_type') == 'vlan' and \
1668 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001669 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001670 ips=[]
1671 #look for floating ip address
tiernob42fd9b2018-06-20 10:44:32 +02001672 try:
1673 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1674 if floating_ip_dict.get("floatingips"):
1675 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
1676 except Exception:
1677 pass
tierno7edb6752016-03-21 17:37:52 +01001678
tiernoae4a8d12016-07-08 12:30:39 +02001679 for subnet in port["fixed_ips"]:
1680 ips.append(subnet["ip_address"])
1681 interface["ip_address"] = ";".join(ips)
1682 vm["interfaces"].append(interface)
1683 except Exception as e:
tiernob42fd9b2018-06-20 10:44:32 +02001684 self.logger.error("Error getting vm interface information {}: {}".format(type(e).__name__, e),
1685 exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +02001686 except vimconn.vimconnNotFoundException as e:
1687 self.logger.error("Exception getting vm status: %s", str(e))
1688 vm['status'] = "DELETED"
1689 vm['error_msg'] = str(e)
1690 except vimconn.vimconnException as e:
1691 self.logger.error("Exception getting vm status: %s", str(e))
1692 vm['status'] = "VIM_ERROR"
1693 vm['error_msg'] = str(e)
1694 vm_dict[vm_id] = vm
1695 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001696
tierno98e909c2017-10-14 13:27:03 +02001697 def action_vminstance(self, vm_id, action_dict, created_items={}):
tierno7edb6752016-03-21 17:37:52 +01001698 '''Send and action over a VM instance from VIM
tierno98e909c2017-10-14 13:27:03 +02001699 Returns None or the console dict if the action was successfully sent to the VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001700 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001701 try:
1702 self._reload_connection()
1703 server = self.nova.servers.find(id=vm_id)
1704 if "start" in action_dict:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001705 if action_dict["start"]=="rebuild":
tierno7edb6752016-03-21 17:37:52 +01001706 server.rebuild()
1707 else:
1708 if server.status=="PAUSED":
1709 server.unpause()
1710 elif server.status=="SUSPENDED":
1711 server.resume()
1712 elif server.status=="SHUTOFF":
1713 server.start()
1714 elif "pause" in action_dict:
1715 server.pause()
1716 elif "resume" in action_dict:
1717 server.resume()
1718 elif "shutoff" in action_dict or "shutdown" in action_dict:
1719 server.stop()
1720 elif "forceOff" in action_dict:
1721 server.stop() #TODO
1722 elif "terminate" in action_dict:
1723 server.delete()
1724 elif "createImage" in action_dict:
1725 server.create_image()
1726 #"path":path_schema,
1727 #"description":description_schema,
1728 #"name":name_schema,
1729 #"metadata":metadata_schema,
1730 #"imageRef": id_schema,
1731 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1732 elif "rebuild" in action_dict:
1733 server.rebuild(server.image['id'])
1734 elif "reboot" in action_dict:
1735 server.reboot() #reboot_type='SOFT'
1736 elif "console" in action_dict:
1737 console_type = action_dict["console"]
1738 if console_type == None or console_type == "novnc":
1739 console_dict = server.get_vnc_console("novnc")
1740 elif console_type == "xvpvnc":
1741 console_dict = server.get_vnc_console(console_type)
1742 elif console_type == "rdp-html5":
1743 console_dict = server.get_rdp_console(console_type)
1744 elif console_type == "spice-html5":
1745 console_dict = server.get_spice_console(console_type)
1746 else:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001747 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
tiernoae4a8d12016-07-08 12:30:39 +02001748 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001749 try:
1750 console_url = console_dict["console"]["url"]
1751 #parse console_url
1752 protocol_index = console_url.find("//")
1753 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1754 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1755 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001756 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001757 console_dict2={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001758 "server": console_url[protocol_index+2 : port_index],
1759 "port": int(console_url[port_index+1 : suffix_index]),
1760 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001761 }
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001762 return console_dict2
tiernoae4a8d12016-07-08 12:30:39 +02001763 except Exception as e:
1764 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001765
tierno98e909c2017-10-14 13:27:03 +02001766 return None
tierno8e995ce2016-09-22 08:13:00 +00001767 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001768 self._format_exception(e)
1769 #TODO insert exception vimconn.HTTP_Unauthorized
1770
kate721d79b2017-06-24 04:21:38 -07001771 ####### VIO Specific Changes #########
garciadeblasebd66722019-01-31 16:01:31 +00001772 def _generate_vlanID(self):
kate721d79b2017-06-24 04:21:38 -07001773 """
1774 Method to get unused vlanID
1775 Args:
1776 None
1777 Returns:
1778 vlanID
1779 """
1780 #Get used VLAN IDs
1781 usedVlanIDs = []
1782 networks = self.get_network_list()
1783 for net in networks:
1784 if net.get('provider:segmentation_id'):
1785 usedVlanIDs.append(net.get('provider:segmentation_id'))
1786 used_vlanIDs = set(usedVlanIDs)
1787
1788 #find unused VLAN ID
1789 for vlanID_range in self.config.get('dataplane_net_vlan_range'):
1790 try:
1791 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
tierno7d782ef2019-10-04 12:56:31 +00001792 for vlanID in range(start_vlanid, end_vlanid + 1):
kate721d79b2017-06-24 04:21:38 -07001793 if vlanID not in used_vlanIDs:
1794 return vlanID
1795 except Exception as exp:
1796 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1797 else:
1798 raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1799 " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
1800
1801
garciadeblasebd66722019-01-31 16:01:31 +00001802 def _generate_multisegment_vlanID(self):
1803 """
1804 Method to get unused vlanID
1805 Args:
1806 None
1807 Returns:
1808 vlanID
1809 """
tierno6869ae72020-01-09 17:37:34 +00001810 # Get used VLAN IDs
garciadeblasebd66722019-01-31 16:01:31 +00001811 usedVlanIDs = []
1812 networks = self.get_network_list()
1813 for net in networks:
1814 if net.get('provider:network_type') == "vlan" and net.get('provider:segmentation_id'):
1815 usedVlanIDs.append(net.get('provider:segmentation_id'))
1816 elif net.get('segments'):
1817 for segment in net.get('segments'):
1818 if segment.get('provider:network_type') == "vlan" and segment.get('provider:segmentation_id'):
1819 usedVlanIDs.append(segment.get('provider:segmentation_id'))
1820 used_vlanIDs = set(usedVlanIDs)
1821
tierno6869ae72020-01-09 17:37:34 +00001822 # find unused VLAN ID
garciadeblasebd66722019-01-31 16:01:31 +00001823 for vlanID_range in self.config.get('multisegment_vlan_range'):
1824 try:
tierno6869ae72020-01-09 17:37:34 +00001825 start_vlanid, end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
tierno7d782ef2019-10-04 12:56:31 +00001826 for vlanID in range(start_vlanid, end_vlanid + 1):
garciadeblasebd66722019-01-31 16:01:31 +00001827 if vlanID not in used_vlanIDs:
1828 return vlanID
1829 except Exception as exp:
1830 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1831 else:
tierno6869ae72020-01-09 17:37:34 +00001832 raise vimconn.vimconnConflictException("Unable to create the VLAN segment."
garciadeblasebd66722019-01-31 16:01:31 +00001833 " All VLAN IDs {} are in use.".format(self.config.get('multisegment_vlan_range')))
1834
1835
1836 def _validate_vlan_ranges(self, input_vlan_range, text_vlan_range):
kate721d79b2017-06-24 04:21:38 -07001837 """
1838 Method to validate user given vlanID ranges
1839 Args: None
1840 Returns: None
1841 """
garciadeblasebd66722019-01-31 16:01:31 +00001842 for vlanID_range in input_vlan_range:
kate721d79b2017-06-24 04:21:38 -07001843 vlan_range = vlanID_range.replace(" ", "")
1844 #validate format
1845 vlanID_pattern = r'(\d)*-(\d)*$'
1846 match_obj = re.match(vlanID_pattern, vlan_range)
1847 if not match_obj:
garciadeblasebd66722019-01-31 16:01:31 +00001848 raise vimconn.vimconnConflictException("Invalid VLAN range for {}: {}.You must provide "\
1849 "'{}' in format [start_ID - end_ID].".format(text_vlan_range, vlanID_range, text_vlan_range))
kate721d79b2017-06-24 04:21:38 -07001850
1851 start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
1852 if start_vlanid <= 0 :
garciadeblasebd66722019-01-31 16:01:31 +00001853 raise vimconn.vimconnConflictException("Invalid VLAN range for {}: {}."\
kate721d79b2017-06-24 04:21:38 -07001854 "Start ID can not be zero. For VLAN "\
garciadeblasebd66722019-01-31 16:01:31 +00001855 "networks valid IDs are 1 to 4094 ".format(text_vlan_range, vlanID_range))
kate721d79b2017-06-24 04:21:38 -07001856 if end_vlanid > 4094 :
garciadeblasebd66722019-01-31 16:01:31 +00001857 raise vimconn.vimconnConflictException("Invalid VLAN range for {}: {}."\
kate721d79b2017-06-24 04:21:38 -07001858 "End VLAN ID can not be greater than 4094. For VLAN "\
garciadeblasebd66722019-01-31 16:01:31 +00001859 "networks valid IDs are 1 to 4094 ".format(text_vlan_range, vlanID_range))
kate721d79b2017-06-24 04:21:38 -07001860
1861 if start_vlanid > end_vlanid:
garciadeblasebd66722019-01-31 16:01:31 +00001862 raise vimconn.vimconnConflictException("Invalid VLAN range for {}: {}."\
1863 "You must provide '{}' in format start_ID - end_ID and "\
1864 "start_ID < end_ID ".format(text_vlan_range, vlanID_range, text_vlan_range))
kate721d79b2017-06-24 04:21:38 -07001865
tiernoae4a8d12016-07-08 12:30:39 +02001866#NOT USED FUNCTIONS
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001867
tiernoae4a8d12016-07-08 12:30:39 +02001868 def new_external_port(self, port_data):
1869 #TODO openstack if needed
1870 '''Adds a external port to VIM'''
1871 '''Returns the port identifier'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001872 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1873
tiernoae4a8d12016-07-08 12:30:39 +02001874 def connect_port_network(self, port_id, network_id, admin=False):
1875 #TODO openstack if needed
1876 '''Connects a external port to a network'''
1877 '''Returns status code of the VIM response'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001878 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1879
tiernoae4a8d12016-07-08 12:30:39 +02001880 def new_user(self, user_name, user_passwd, tenant_id=None):
1881 '''Adds a new user to openstack VIM'''
1882 '''Returns the user identifier'''
1883 self.logger.debug("osconnector: Adding a new user to VIM")
1884 try:
1885 self._reload_connection()
Eduardo Sousae3c0dbc2018-09-03 11:56:07 +01001886 user=self.keystone.users.create(user_name, password=user_passwd, default_project=tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +02001887 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1888 return user.id
1889 except ksExceptions.ConnectionError as e:
1890 error_value=-vimconn.HTTP_Bad_Request
1891 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1892 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001893 error_value=-vimconn.HTTP_Bad_Request
1894 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1895 #TODO insert exception vimconn.HTTP_Unauthorized
1896 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001897 self.logger.debug("new_user " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001898 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001899
1900 def delete_user(self, user_id):
1901 '''Delete a user from openstack VIM'''
1902 '''Returns the user identifier'''
1903 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001904 print("osconnector: Deleting a user from VIM")
tiernoae4a8d12016-07-08 12:30:39 +02001905 try:
1906 self._reload_connection()
1907 self.keystone.users.delete(user_id)
1908 return 1, user_id
1909 except ksExceptions.ConnectionError as e:
1910 error_value=-vimconn.HTTP_Bad_Request
1911 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1912 except ksExceptions.NotFound as e:
1913 error_value=-vimconn.HTTP_Not_Found
1914 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1915 except ksExceptions.ClientException as e: #TODO remove
1916 error_value=-vimconn.HTTP_Bad_Request
1917 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1918 #TODO insert exception vimconn.HTTP_Unauthorized
1919 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001920 self.logger.debug("delete_tenant " + error_text)
tiernoae4a8d12016-07-08 12:30:39 +02001921 return error_value, error_text
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001922
tierno7edb6752016-03-21 17:37:52 +01001923 def get_hosts_info(self):
1924 '''Get the information of deployed hosts
1925 Returns the hosts content'''
1926 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001927 print("osconnector: Getting Host info from VIM")
tierno7edb6752016-03-21 17:37:52 +01001928 try:
1929 h_list=[]
1930 self._reload_connection()
1931 hypervisors = self.nova.hypervisors.list()
1932 for hype in hypervisors:
1933 h_list.append( hype.to_dict() )
1934 return 1, {"hosts":h_list}
1935 except nvExceptions.NotFound as e:
1936 error_value=-vimconn.HTTP_Not_Found
1937 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1938 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1939 error_value=-vimconn.HTTP_Bad_Request
1940 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1941 #TODO insert exception vimconn.HTTP_Unauthorized
1942 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001943 self.logger.debug("get_hosts_info " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001944 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001945
1946 def get_hosts(self, vim_tenant):
1947 '''Get the hosts and deployed instances
1948 Returns the hosts content'''
1949 r, hype_dict = self.get_hosts_info()
1950 if r<0:
1951 return r, hype_dict
1952 hypervisors = hype_dict["hosts"]
1953 try:
1954 servers = self.nova.servers.list()
1955 for hype in hypervisors:
1956 for server in servers:
1957 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1958 if 'vm' in hype:
1959 hype['vm'].append(server.id)
1960 else:
1961 hype['vm'] = [server.id]
1962 return 1, hype_dict
1963 except nvExceptions.NotFound as e:
1964 error_value=-vimconn.HTTP_Not_Found
1965 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1966 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1967 error_value=-vimconn.HTTP_Bad_Request
1968 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1969 #TODO insert exception vimconn.HTTP_Unauthorized
1970 #if reaching here is because an exception
tierno9c5c8322018-03-23 15:44:03 +01001971 self.logger.debug("get_hosts " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001972 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001973
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001974 def new_classification(self, name, ctype, definition):
tierno7d782ef2019-10-04 12:56:31 +00001975 self.logger.debug('Adding a new (Traffic) Classification to VIM, named %s', name)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001976 try:
1977 new_class = None
1978 self._reload_connection()
1979 if ctype not in supportedClassificationTypes:
1980 raise vimconn.vimconnNotSupportedException(
1981 'OpenStack VIM connector doesn\'t support provided '
1982 'Classification Type {}, supported ones are: '
1983 '{}'.format(ctype, supportedClassificationTypes))
1984 if not self._validate_classification(ctype, definition):
1985 raise vimconn.vimconnException(
1986 'Incorrect Classification definition '
1987 'for the type specified.')
1988 classification_dict = definition
1989 classification_dict['name'] = name
tierno7edb6752016-03-21 17:37:52 +01001990
Igor D.Ccaadc442017-11-06 12:48:48 +00001991 new_class = self.neutron.create_sfc_flow_classifier(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001992 {'flow_classifier': classification_dict})
1993 return new_class['flow_classifier']['id']
1994 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1995 neExceptions.NeutronException, ConnectionError) as e:
1996 self.logger.error(
1997 'Creation of Classification failed.')
1998 self._format_exception(e)
1999
2000 def get_classification(self, class_id):
2001 self.logger.debug(" Getting Classification %s from VIM", class_id)
2002 filter_dict = {"id": class_id}
2003 class_list = self.get_classification_list(filter_dict)
2004 if len(class_list) == 0:
2005 raise vimconn.vimconnNotFoundException(
2006 "Classification '{}' not found".format(class_id))
2007 elif len(class_list) > 1:
2008 raise vimconn.vimconnConflictException(
2009 "Found more than one Classification with this criteria")
2010 classification = class_list[0]
2011 return classification
2012
2013 def get_classification_list(self, filter_dict={}):
2014 self.logger.debug("Getting Classifications from VIM filter: '%s'",
2015 str(filter_dict))
2016 try:
tierno69b590e2018-03-13 18:52:23 +01002017 filter_dict_os = filter_dict.copy()
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002018 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01002019 if self.api_version3 and "tenant_id" in filter_dict_os:
2020 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
Igor D.Ccaadc442017-11-06 12:48:48 +00002021 classification_dict = self.neutron.list_sfc_flow_classifiers(
tierno69b590e2018-03-13 18:52:23 +01002022 **filter_dict_os)
2023 classification_list = classification_dict["flow_classifiers"]
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002024 self.__classification_os2mano(classification_list)
2025 return classification_list
2026 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2027 neExceptions.NeutronException, ConnectionError) as e:
2028 self._format_exception(e)
2029
2030 def delete_classification(self, class_id):
2031 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
2032 try:
2033 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00002034 self.neutron.delete_sfc_flow_classifier(class_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002035 return class_id
2036 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2037 ksExceptions.ClientException, neExceptions.NeutronException,
2038 ConnectionError) as e:
2039 self._format_exception(e)
2040
2041 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
tierno7d782ef2019-10-04 12:56:31 +00002042 self.logger.debug("Adding a new Service Function Instance to VIM, named '%s'", name)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002043 try:
2044 new_sfi = None
2045 self._reload_connection()
2046 correlation = None
2047 if sfc_encap:
Igor D.Ccaadc442017-11-06 12:48:48 +00002048 correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002049 if len(ingress_ports) != 1:
2050 raise vimconn.vimconnNotSupportedException(
2051 "OpenStack VIM connector can only have "
2052 "1 ingress port per SFI")
2053 if len(egress_ports) != 1:
2054 raise vimconn.vimconnNotSupportedException(
2055 "OpenStack VIM connector can only have "
2056 "1 egress port per SFI")
2057 sfi_dict = {'name': name,
2058 'ingress': ingress_ports[0],
2059 'egress': egress_ports[0],
2060 'service_function_parameters': {
2061 'correlation': correlation}}
Igor D.Ccaadc442017-11-06 12:48:48 +00002062 new_sfi = self.neutron.create_sfc_port_pair({'port_pair': sfi_dict})
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002063 return new_sfi['port_pair']['id']
2064 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2065 neExceptions.NeutronException, ConnectionError) as e:
2066 if new_sfi:
2067 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00002068 self.neutron.delete_sfc_port_pair(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002069 new_sfi['port_pair']['id'])
2070 except Exception:
2071 self.logger.error(
2072 'Creation of Service Function Instance failed, with '
2073 'subsequent deletion failure as well.')
2074 self._format_exception(e)
2075
2076 def get_sfi(self, sfi_id):
tierno7d782ef2019-10-04 12:56:31 +00002077 self.logger.debug('Getting Service Function Instance %s from VIM', sfi_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002078 filter_dict = {"id": sfi_id}
2079 sfi_list = self.get_sfi_list(filter_dict)
2080 if len(sfi_list) == 0:
tierno7d782ef2019-10-04 12:56:31 +00002081 raise vimconn.vimconnNotFoundException("Service Function Instance '{}' not found".format(sfi_id))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002082 elif len(sfi_list) > 1:
2083 raise vimconn.vimconnConflictException(
2084 'Found more than one Service Function Instance '
2085 'with this criteria')
2086 sfi = sfi_list[0]
2087 return sfi
2088
2089 def get_sfi_list(self, filter_dict={}):
tierno7d782ef2019-10-04 12:56:31 +00002090 self.logger.debug("Getting Service Function Instances from VIM filter: '%s'", str(filter_dict))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002091 try:
2092 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01002093 filter_dict_os = filter_dict.copy()
2094 if self.api_version3 and "tenant_id" in filter_dict_os:
2095 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
2096 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002097 sfi_list = sfi_dict["port_pairs"]
2098 self.__sfi_os2mano(sfi_list)
2099 return sfi_list
2100 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2101 neExceptions.NeutronException, ConnectionError) as e:
2102 self._format_exception(e)
2103
2104 def delete_sfi(self, sfi_id):
2105 self.logger.debug("Deleting Service Function Instance '%s' "
2106 "from VIM", sfi_id)
2107 try:
2108 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00002109 self.neutron.delete_sfc_port_pair(sfi_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002110 return sfi_id
2111 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2112 ksExceptions.ClientException, neExceptions.NeutronException,
2113 ConnectionError) as e:
2114 self._format_exception(e)
2115
2116 def new_sf(self, name, sfis, sfc_encap=True):
tierno7d782ef2019-10-04 12:56:31 +00002117 self.logger.debug("Adding a new Service Function to VIM, named '%s'", name)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002118 try:
2119 new_sf = None
2120 self._reload_connection()
tierno9c5c8322018-03-23 15:44:03 +01002121 # correlation = None
2122 # if sfc_encap:
2123 # correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002124 for instance in sfis:
2125 sfi = self.get_sfi(instance)
Igor D.Ccaadc442017-11-06 12:48:48 +00002126 if sfi.get('sfc_encap') != sfc_encap:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002127 raise vimconn.vimconnNotSupportedException(
2128 "OpenStack VIM connector requires all SFIs of the "
2129 "same SF to share the same SFC Encapsulation")
2130 sf_dict = {'name': name,
2131 'port_pairs': sfis}
Igor D.Ccaadc442017-11-06 12:48:48 +00002132 new_sf = self.neutron.create_sfc_port_pair_group({
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002133 'port_pair_group': sf_dict})
2134 return new_sf['port_pair_group']['id']
2135 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2136 neExceptions.NeutronException, ConnectionError) as e:
2137 if new_sf:
2138 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00002139 self.neutron.delete_sfc_port_pair_group(
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002140 new_sf['port_pair_group']['id'])
2141 except Exception:
2142 self.logger.error(
2143 'Creation of Service Function failed, with '
2144 'subsequent deletion failure as well.')
2145 self._format_exception(e)
2146
2147 def get_sf(self, sf_id):
2148 self.logger.debug("Getting Service Function %s from VIM", sf_id)
2149 filter_dict = {"id": sf_id}
2150 sf_list = self.get_sf_list(filter_dict)
2151 if len(sf_list) == 0:
2152 raise vimconn.vimconnNotFoundException(
2153 "Service Function '{}' not found".format(sf_id))
2154 elif len(sf_list) > 1:
2155 raise vimconn.vimconnConflictException(
2156 "Found more than one Service Function with this criteria")
2157 sf = sf_list[0]
2158 return sf
2159
2160 def get_sf_list(self, filter_dict={}):
2161 self.logger.debug("Getting Service Function from VIM filter: '%s'",
2162 str(filter_dict))
2163 try:
2164 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01002165 filter_dict_os = filter_dict.copy()
2166 if self.api_version3 and "tenant_id" in filter_dict_os:
2167 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
2168 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002169 sf_list = sf_dict["port_pair_groups"]
2170 self.__sf_os2mano(sf_list)
2171 return sf_list
2172 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2173 neExceptions.NeutronException, ConnectionError) as e:
2174 self._format_exception(e)
2175
2176 def delete_sf(self, sf_id):
2177 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
2178 try:
2179 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00002180 self.neutron.delete_sfc_port_pair_group(sf_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002181 return sf_id
2182 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2183 ksExceptions.ClientException, neExceptions.NeutronException,
2184 ConnectionError) as e:
2185 self._format_exception(e)
2186
2187 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
tierno7d782ef2019-10-04 12:56:31 +00002188 self.logger.debug("Adding a new Service Function Path to VIM, named '%s'", name)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002189 try:
2190 new_sfp = None
2191 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00002192 # In networking-sfc the MPLS encapsulation is legacy
2193 # should be used when no full SFC Encapsulation is intended
schillinge981df9a2019-01-24 09:25:11 +01002194 correlation = 'mpls'
Igor D.Ccaadc442017-11-06 12:48:48 +00002195 if sfc_encap:
2196 correlation = 'nsh'
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002197 sfp_dict = {'name': name,
2198 'flow_classifiers': classifications,
2199 'port_pair_groups': sfs,
2200 'chain_parameters': {'correlation': correlation}}
2201 if spi:
2202 sfp_dict['chain_id'] = spi
Igor D.Ccaadc442017-11-06 12:48:48 +00002203 new_sfp = self.neutron.create_sfc_port_chain({'port_chain': sfp_dict})
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002204 return new_sfp["port_chain"]["id"]
2205 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2206 neExceptions.NeutronException, ConnectionError) as e:
2207 if new_sfp:
2208 try:
Igor D.Ccaadc442017-11-06 12:48:48 +00002209 self.neutron.delete_sfc_port_chain(new_sfp['port_chain']['id'])
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002210 except Exception:
2211 self.logger.error(
2212 'Creation of Service Function Path failed, with '
2213 'subsequent deletion failure as well.')
2214 self._format_exception(e)
2215
2216 def get_sfp(self, sfp_id):
2217 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
2218 filter_dict = {"id": sfp_id}
2219 sfp_list = self.get_sfp_list(filter_dict)
2220 if len(sfp_list) == 0:
2221 raise vimconn.vimconnNotFoundException(
2222 "Service Function Path '{}' not found".format(sfp_id))
2223 elif len(sfp_list) > 1:
2224 raise vimconn.vimconnConflictException(
2225 "Found more than one Service Function Path with this criteria")
2226 sfp = sfp_list[0]
2227 return sfp
2228
2229 def get_sfp_list(self, filter_dict={}):
tierno7d782ef2019-10-04 12:56:31 +00002230 self.logger.debug("Getting Service Function Paths from VIM filter: '%s'", str(filter_dict))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002231 try:
2232 self._reload_connection()
tierno69b590e2018-03-13 18:52:23 +01002233 filter_dict_os = filter_dict.copy()
2234 if self.api_version3 and "tenant_id" in filter_dict_os:
2235 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
2236 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002237 sfp_list = sfp_dict["port_chains"]
2238 self.__sfp_os2mano(sfp_list)
2239 return sfp_list
2240 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2241 neExceptions.NeutronException, ConnectionError) as e:
2242 self._format_exception(e)
2243
2244 def delete_sfp(self, sfp_id):
tierno7d782ef2019-10-04 12:56:31 +00002245 self.logger.debug("Deleting Service Function Path '%s' from VIM", sfp_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002246 try:
2247 self._reload_connection()
Igor D.Ccaadc442017-11-06 12:48:48 +00002248 self.neutron.delete_sfc_port_chain(sfp_id)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00002249 return sfp_id
2250 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2251 ksExceptions.ClientException, neExceptions.NeutronException,
2252 ConnectionError) as e:
2253 self._format_exception(e)
borsatti8a2dda32019-12-18 15:08:57 +00002254
2255
2256 def refresh_sfps_status(self, sfp_list):
2257 '''Get the status of the service function path
2258 Params: the list of sfp identifiers
2259 Returns a dictionary with:
2260 vm_id: #VIM id of this service function path
2261 status: #Mandatory. Text with one of:
2262 # DELETED (not found at vim)
2263 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
2264 # OTHER (Vim reported other status not understood)
2265 # ERROR (VIM indicates an ERROR status)
2266 # ACTIVE,
2267 # CREATING (on building process)
2268 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
2269 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)F
2270 '''
2271 sfp_dict={}
2272 self.logger.debug("refresh_sfps status: Getting tenant SFP information from VIM")
2273 for sfp_id in sfp_list:
2274 sfp={}
2275 try:
2276 sfp_vim = self.get_sfp(sfp_id)
2277 if sfp_vim['spi']:
2278 sfp['status'] = vmStatus2manoFormat[ 'ACTIVE' ]
2279 else:
2280 sfp['status'] = "OTHER"
2281 sfp['error_msg'] = "VIM status reported " + vm_vim['status']
2282
2283 sfp['vim_info'] = self.serialize(sfp_vim)
2284
2285 if sfp_vim.get('fault'):
2286 sfp['error_msg'] = str(sfp_vim['fault'])
2287
2288 except vimconn.vimconnNotFoundException as e:
2289 self.logger.error("Exception getting sfp status: %s", str(e))
2290 sfp['status'] = "DELETED"
2291 sfp['error_msg'] = str(e)
2292 except vimconn.vimconnException as e:
2293 self.logger.error("Exception getting sfp status: %s", str(e))
2294 sfp['status'] = "VIM_ERROR"
2295 sfp['error_msg'] = str(e)
2296 sfp_dict[sfp_id] = sfp
2297 return sfp_dict
2298
2299
2300 def refresh_sfis_status(self, sfi_list):
2301 '''Get the status of the service function instances
2302 Params: the list of sfi identifiers
2303 Returns a dictionary with:
2304 vm_id: #VIM id of this service function instance
2305 status: #Mandatory. Text with one of:
2306 # DELETED (not found at vim)
2307 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
2308 # OTHER (Vim reported other status not understood)
2309 # ERROR (VIM indicates an ERROR status)
2310 # ACTIVE,
2311 # CREATING (on building process)
2312 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
2313 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
2314 '''
2315 sfi_dict={}
2316 self.logger.debug("refresh_sfis status: Getting tenant sfi information from VIM")
2317 for sfi_id in sfi_list:
2318 sfi={}
2319 try:
2320 sfi_vim = self.get_sfi(sfi_id)
2321 if sfi_vim:
2322 sfi['status'] = vmStatus2manoFormat[ 'ACTIVE' ]
2323 else:
2324 sfi['status'] = "OTHER"
2325 sfi['error_msg'] = "VIM status reported " + vm_vim['status']
2326
2327 sfi['vim_info'] = self.serialize(sfi_vim)
2328
2329 if sfi_vim.get('fault'):
2330 sfi['error_msg'] = str(sfi_vim['fault'])
2331
2332 except vimconn.vimconnNotFoundException as e:
2333 self.logger.error("Exception getting sfi status: %s", str(e))
2334 sfi['status'] = "DELETED"
2335 sfi['error_msg'] = str(e)
2336 except vimconn.vimconnException as e:
2337 self.logger.error("Exception getting sfi status: %s", str(e))
2338 sfi['status'] = "VIM_ERROR"
2339 sfi['error_msg'] = str(e)
2340 sfi_dict[sfi_id] = sfi
2341 return sfi_dict
2342
2343
2344 def refresh_sfs_status(self, sf_list):
2345 '''Get the status of the service functions
2346 Params: the list of sf identifiers
2347 Returns a dictionary with:
2348 vm_id: #VIM id of this service function
2349 status: #Mandatory. Text with one of:
2350 # DELETED (not found at vim)
2351 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
2352 # OTHER (Vim reported other status not understood)
2353 # ERROR (VIM indicates an ERROR status)
2354 # ACTIVE,
2355 # CREATING (on building process)
2356 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
2357 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
2358 '''
2359 sf_dict={}
2360 self.logger.debug("refresh_sfs status: Getting tenant sf information from VIM")
2361 for sf_id in sf_list:
2362 sf={}
2363 try:
2364 sf_vim = self.get_sf(sf_id)
2365 if sf_vim:
2366 sf['status'] = vmStatus2manoFormat[ 'ACTIVE' ]
2367 else:
2368 sf['status'] = "OTHER"
2369 sf['error_msg'] = "VIM status reported " + vm_vim['status']
2370
2371 sf['vim_info'] = self.serialize(sf_vim)
2372
2373 if sf_vim.get('fault'):
2374 sf['error_msg'] = str(sf_vim['fault'])
2375
2376 except vimconn.vimconnNotFoundException as e:
2377 self.logger.error("Exception getting sf status: %s", str(e))
2378 sf['status'] = "DELETED"
2379 sf['error_msg'] = str(e)
2380 except vimconn.vimconnException as e:
2381 self.logger.error("Exception getting sf status: %s", str(e))
2382 sf['status'] = "VIM_ERROR"
2383 sf['error_msg'] = str(e)
2384 sf_dict[sf_id] = sf
2385 return sf_dict
2386
2387
2388
2389 def refresh_classifications_status(self, classification_list):
2390 '''Get the status of the classifications
2391 Params: the list of classification identifiers
2392 Returns a dictionary with:
2393 vm_id: #VIM id of this classifier
2394 status: #Mandatory. Text with one of:
2395 # DELETED (not found at vim)
2396 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
2397 # OTHER (Vim reported other status not understood)
2398 # ERROR (VIM indicates an ERROR status)
2399 # ACTIVE,
2400 # CREATING (on building process)
2401 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
2402 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
2403 '''
2404 classification_dict={}
2405 self.logger.debug("refresh_classifications status: Getting tenant classification information from VIM")
2406 for classification_id in classification_list:
2407 classification={}
2408 try:
2409 classification_vim = self.get_classification(classification_id)
2410 if classification_vim:
2411 classification['status'] = vmStatus2manoFormat[ 'ACTIVE' ]
2412 else:
2413 classification['status'] = "OTHER"
2414 classification['error_msg'] = "VIM status reported " + vm_vim['status']
2415
2416 classification['vim_info'] = self.serialize(classification_vim)
2417
2418 if classification_vim.get('fault'):
2419 classification['error_msg'] = str(classification_vim['fault'])
2420
2421 except vimconn.vimconnNotFoundException as e:
2422 self.logger.error("Exception getting classification status: %s", str(e))
2423 classification['status'] = "DELETED"
2424 classification['error_msg'] = str(e)
2425 except vimconn.vimconnException as e:
2426 self.logger.error("Exception getting classification status: %s", str(e))
2427 classification['status'] = "VIM_ERROR"
2428 classification['error_msg'] = str(e)
2429 classification_dict[classification_id] = classification
2430 return classification_dict