blob: c839d655336af86a481da68c328fd977b667ecea [file] [log] [blame]
tierno7edb6752016-03-21 17:37:52 +01001# -*- coding: utf-8 -*-
2
3##
4# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
5# This file is part of openmano
6# All Rights Reserved.
7#
8# Licensed under the Apache License, Version 2.0 (the "License"); you may
9# not use this file except in compliance with the License. You may obtain
10# a copy of the License at
11#
12# http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17# License for the specific language governing permissions and limitations
18# under the License.
19#
20# For those usages not covered by the Apache License, Version 2.0 please
21# contact with: nfvlabs@tid.es
22##
23
24'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000025osconnector implements all the methods to interact with openstack using the python-neutronclient.
26
27For the VNF forwarding graph, The OpenStack VIM connector calls the
28networking-sfc Neutron extension methods, whose resources are mapped
29to the VIM connector's SFC resources as follows:
30- Classification (OSM) -> Flow Classifier (Neutron)
31- Service Function Instance (OSM) -> Port Pair (Neutron)
32- Service Function (OSM) -> Port Pair Group (Neutron)
33- Service Function Path (OSM) -> Port Chain (Neutron)
tierno7edb6752016-03-21 17:37:52 +010034'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000035__author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C."
36__date__ = "$22-sep-2017 23:59:59$"
tierno7edb6752016-03-21 17:37:52 +010037
38import vimconn
39import json
tiernoae4a8d12016-07-08 12:30:39 +020040import logging
garciadeblas9f8456e2016-09-05 05:02:59 +020041import netaddr
montesmoreno0c8def02016-12-22 12:16:23 +000042import time
tierno36c0b172017-01-12 18:32:28 +010043import yaml
garciadeblas2299e3b2017-01-26 14:35:55 +000044import random
tierno40e1bce2017-08-09 09:12:04 +020045import sys
kate721d79b2017-06-24 04:21:38 -070046import re
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000047import copy
tierno7edb6752016-03-21 17:37:52 +010048
tiernob5cef372017-06-19 15:52:22 +020049from novaclient import client as nClient, exceptions as nvExceptions
50from keystoneauth1.identity import v2, v3
51from keystoneauth1 import session
tierno7edb6752016-03-21 17:37:52 +010052import keystoneclient.exceptions as ksExceptions
tiernof716aea2017-06-21 18:01:40 +020053import keystoneclient.v3.client as ksClient_v3
54import keystoneclient.v2_0.client as ksClient_v2
tiernob5cef372017-06-19 15:52:22 +020055from glanceclient import client as glClient
tierno7edb6752016-03-21 17:37:52 +010056import glanceclient.client as gl1Client
57import glanceclient.exc as gl1Exceptions
tiernob5cef372017-06-19 15:52:22 +020058from cinderclient import client as cClient
tierno7edb6752016-03-21 17:37:52 +010059from httplib import HTTPException
tiernob5cef372017-06-19 15:52:22 +020060from neutronclient.neutron import client as neClient
tierno7edb6752016-03-21 17:37:52 +010061from neutronclient.common import exceptions as neExceptions
62from requests.exceptions import ConnectionError
63
tierno40e1bce2017-08-09 09:12:04 +020064
65"""contain the openstack virtual machine status to openmano status"""
tierno7edb6752016-03-21 17:37:52 +010066vmStatus2manoFormat={'ACTIVE':'ACTIVE',
67 'PAUSED':'PAUSED',
68 'SUSPENDED': 'SUSPENDED',
69 'SHUTOFF':'INACTIVE',
70 'BUILD':'BUILD',
71 'ERROR':'ERROR','DELETED':'DELETED'
72 }
73netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
74 }
75
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +000076supportedClassificationTypes = ['legacy_flow_classifier']
77
montesmoreno0c8def02016-12-22 12:16:23 +000078#global var to have a timeout creating and deleting volumes
79volume_timeout = 60
garciadeblas05a1a612017-07-23 20:26:28 +020080server_timeout = 300
montesmoreno0c8def02016-12-22 12:16:23 +000081
tierno7edb6752016-03-21 17:37:52 +010082class vimconnector(vimconn.vimconnector):
tiernob3d36742017-03-03 23:51:05 +010083 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
84 log_level=None, config={}, persistent_info={}):
ahmadsa96af9f42017-01-31 16:17:14 +050085 '''using common constructor parameters. In this case
tierno7edb6752016-03-21 17:37:52 +010086 'url' is the keystone authorization url,
87 'url_admin' is not use
88 '''
tiernof716aea2017-06-21 18:01:40 +020089 api_version = config.get('APIversion')
90 if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
tiernob5cef372017-06-19 15:52:22 +020091 raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
tiernof716aea2017-06-21 18:01:40 +020092 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
kate721d79b2017-06-24 04:21:38 -070093 vim_type = config.get('vim_type')
94 if vim_type and vim_type not in ('vio', 'VIO'):
95 raise vimconn.vimconnException("Invalid value '{}' for config:vim_type."
96 "Allowed values are 'vio' or 'VIO'".format(vim_type))
97
98 if config.get('dataplane_net_vlan_range') is not None:
99 #validate vlan ranges provided by user
100 self._validate_vlan_ranges(config.get('dataplane_net_vlan_range'))
101
tiernob5cef372017-06-19 15:52:22 +0200102 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
103 config)
tiernob3d36742017-03-03 23:51:05 +0100104
tiernob5cef372017-06-19 15:52:22 +0200105 self.insecure = self.config.get("insecure", False)
tierno7edb6752016-03-21 17:37:52 +0100106 if not url:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000107 raise TypeError('url param can not be NoneType')
tiernob5cef372017-06-19 15:52:22 +0200108 self.persistent_info = persistent_info
mirabal29356312017-07-27 12:21:22 +0200109 self.availability_zone = persistent_info.get('availability_zone', None)
tiernob5cef372017-06-19 15:52:22 +0200110 self.session = persistent_info.get('session', {'reload_client': True})
111 self.nova = self.session.get('nova')
112 self.neutron = self.session.get('neutron')
113 self.cinder = self.session.get('cinder')
114 self.glance = self.session.get('glance')
tiernob39d49e2017-08-02 14:02:15 +0200115 self.glancev1 = self.session.get('glancev1')
tiernof716aea2017-06-21 18:01:40 +0200116 self.keystone = self.session.get('keystone')
117 self.api_version3 = self.session.get('api_version3')
kate721d79b2017-06-24 04:21:38 -0700118 self.vim_type = self.config.get("vim_type")
119 if self.vim_type:
120 self.vim_type = self.vim_type.upper()
121 if self.config.get("use_internal_endpoint"):
122 self.endpoint_type = "internalURL"
123 else:
124 self.endpoint_type = None
montesmoreno0c8def02016-12-22 12:16:23 +0000125
tierno73ad9e42016-09-12 18:11:11 +0200126 self.logger = logging.getLogger('openmano.vim.openstack')
kate721d79b2017-06-24 04:21:38 -0700127
128 ####### VIO Specific Changes #########
129 if self.vim_type == "VIO":
130 self.logger = logging.getLogger('openmano.vim.vio')
131
tiernofe789902016-09-29 14:20:44 +0000132 if log_level:
kate54616752017-09-05 23:26:28 -0700133 self.logger.setLevel( getattr(logging, log_level))
tiernof716aea2017-06-21 18:01:40 +0200134
135 def __getitem__(self, index):
136 """Get individuals parameters.
137 Throw KeyError"""
138 if index == 'project_domain_id':
139 return self.config.get("project_domain_id")
140 elif index == 'user_domain_id':
141 return self.config.get("user_domain_id")
142 else:
tierno76a3c312017-06-29 16:42:15 +0200143 return vimconn.vimconnector.__getitem__(self, index)
tiernof716aea2017-06-21 18:01:40 +0200144
145 def __setitem__(self, index, value):
146 """Set individuals parameters and it is marked as dirty so to force connection reload.
147 Throw KeyError"""
148 if index == 'project_domain_id':
149 self.config["project_domain_id"] = value
150 elif index == 'user_domain_id':
151 self.config["user_domain_id"] = value
152 else:
153 vimconn.vimconnector.__setitem__(self, index, value)
tiernob5cef372017-06-19 15:52:22 +0200154 self.session['reload_client'] = True
tiernof716aea2017-06-21 18:01:40 +0200155
tierno7edb6752016-03-21 17:37:52 +0100156 def _reload_connection(self):
157 '''Called before any operation, it check if credentials has changed
158 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
159 '''
160 #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 +0200161 if self.session['reload_client']:
tiernof716aea2017-06-21 18:01:40 +0200162 if self.config.get('APIversion'):
163 self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3'
164 else: # get from ending auth_url that end with v3 or with v2.0
165 self.api_version3 = self.url.split("/")[-1] == "v3"
166 self.session['api_version3'] = self.api_version3
167 if self.api_version3:
168 auth = v3.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200169 username=self.user,
170 password=self.passwd,
171 project_name=self.tenant_name,
172 project_id=self.tenant_id,
173 project_domain_id=self.config.get('project_domain_id', 'default'),
174 user_domain_id=self.config.get('user_domain_id', 'default'))
ahmadsa95baa272016-11-30 09:14:11 +0500175 else:
tiernof716aea2017-06-21 18:01:40 +0200176 auth = v2.Password(auth_url=self.url,
tiernob5cef372017-06-19 15:52:22 +0200177 username=self.user,
178 password=self.passwd,
179 tenant_name=self.tenant_name,
180 tenant_id=self.tenant_id)
181 sess = session.Session(auth=auth, verify=not self.insecure)
tiernof716aea2017-06-21 18:01:40 +0200182 if self.api_version3:
kate721d79b2017-06-24 04:21:38 -0700183 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200184 else:
kate721d79b2017-06-24 04:21:38 -0700185 self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
tiernof716aea2017-06-21 18:01:40 +0200186 self.session['keystone'] = self.keystone
montesmoreno9317d302017-08-16 12:48:23 +0200187 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
188 # This implementation approach is due to the warning message in
189 # https://developer.openstack.org/api-guide/compute/microversions.html
190 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
191 # always require an specific microversion.
192 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
193 version = self.config.get("microversion")
194 if not version:
195 version = "2.1"
kate54616752017-09-05 23:26:28 -0700196 self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type)
kate721d79b2017-06-24 04:21:38 -0700197 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type)
198 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type)
199 if self.endpoint_type == "internalURL":
200 glance_service_id = self.keystone.services.list(name="glance")[0].id
201 glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
202 else:
203 glance_endpoint = None
204 self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
205 #using version 1 of glance client in new_image()
206 self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
207 endpoint=glance_endpoint)
tiernob5cef372017-06-19 15:52:22 +0200208 self.session['reload_client'] = False
209 self.persistent_info['session'] = self.session
mirabal29356312017-07-27 12:21:22 +0200210 # add availablity zone info inside self.persistent_info
211 self._set_availablity_zones()
212 self.persistent_info['availability_zone'] = self.availability_zone
ahmadsa95baa272016-11-30 09:14:11 +0500213
tierno7edb6752016-03-21 17:37:52 +0100214 def __net_os2mano(self, net_list_dict):
215 '''Transform the net openstack format to mano format
216 net_list_dict can be a list of dict or a single dict'''
217 if type(net_list_dict) is dict:
218 net_list_=(net_list_dict,)
219 elif type(net_list_dict) is list:
220 net_list_=net_list_dict
221 else:
222 raise TypeError("param net_list_dict must be a list or a dictionary")
223 for net in net_list_:
224 if net.get('provider:network_type') == "vlan":
225 net['type']='data'
226 else:
227 net['type']='bridge'
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200228
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000229 def __classification_os2mano(self, class_list_dict):
230 """Transform the openstack format (Flow Classifier) to mano format
231 (Classification) class_list_dict can be a list of dict or a single dict
232 """
233 if isinstance(class_list_dict, dict):
234 class_list_ = [class_list_dict]
235 elif isinstance(class_list_dict, list):
236 class_list_ = class_list_dict
237 else:
238 raise TypeError(
239 "param class_list_dict must be a list or a dictionary")
240 for classification in class_list_:
241 id = classification.pop('id')
242 name = classification.pop('name')
243 description = classification.pop('description')
244 project_id = classification.pop('project_id')
245 tenant_id = classification.pop('tenant_id')
246 original_classification = copy.deepcopy(classification)
247 classification.clear()
248 classification['ctype'] = 'legacy_flow_classifier'
249 classification['definition'] = original_classification
250 classification['id'] = id
251 classification['name'] = name
252 classification['description'] = description
253 classification['project_id'] = project_id
254 classification['tenant_id'] = tenant_id
255
256 def __sfi_os2mano(self, sfi_list_dict):
257 """Transform the openstack format (Port Pair) to mano format (SFI)
258 sfi_list_dict can be a list of dict or a single dict
259 """
260 if isinstance(sfi_list_dict, dict):
261 sfi_list_ = [sfi_list_dict]
262 elif isinstance(sfi_list_dict, list):
263 sfi_list_ = sfi_list_dict
264 else:
265 raise TypeError(
266 "param sfi_list_dict must be a list or a dictionary")
267 for sfi in sfi_list_:
268 sfi['ingress_ports'] = []
269 sfi['egress_ports'] = []
270 if sfi.get('ingress'):
271 sfi['ingress_ports'].append(sfi['ingress'])
272 if sfi.get('egress'):
273 sfi['egress_ports'].append(sfi['egress'])
274 del sfi['ingress']
275 del sfi['egress']
276 params = sfi.get('service_function_parameters')
277 sfc_encap = False
278 if params:
279 correlation = params.get('correlation')
280 if correlation:
281 sfc_encap = True
282 sfi['sfc_encap'] = sfc_encap
283 del sfi['service_function_parameters']
284
285 def __sf_os2mano(self, sf_list_dict):
286 """Transform the openstack format (Port Pair Group) to mano format (SF)
287 sf_list_dict can be a list of dict or a single dict
288 """
289 if isinstance(sf_list_dict, dict):
290 sf_list_ = [sf_list_dict]
291 elif isinstance(sf_list_dict, list):
292 sf_list_ = sf_list_dict
293 else:
294 raise TypeError(
295 "param sf_list_dict must be a list or a dictionary")
296 for sf in sf_list_:
297 del sf['port_pair_group_parameters']
298 sf['sfis'] = sf['port_pairs']
299 del sf['port_pairs']
300
301 def __sfp_os2mano(self, sfp_list_dict):
302 """Transform the openstack format (Port Chain) to mano format (SFP)
303 sfp_list_dict can be a list of dict or a single dict
304 """
305 if isinstance(sfp_list_dict, dict):
306 sfp_list_ = [sfp_list_dict]
307 elif isinstance(sfp_list_dict, list):
308 sfp_list_ = sfp_list_dict
309 else:
310 raise TypeError(
311 "param sfp_list_dict must be a list or a dictionary")
312 for sfp in sfp_list_:
313 params = sfp.pop('chain_parameters')
314 sfc_encap = False
315 if params:
316 correlation = params.get('correlation')
317 if correlation:
318 sfc_encap = True
319 sfp['sfc_encap'] = sfc_encap
320 sfp['spi'] = sfp.pop('chain_id')
321 sfp['classifications'] = sfp.pop('flow_classifiers')
322 sfp['service_functions'] = sfp.pop('port_pair_groups')
323
324 # placeholder for now; read TODO note below
325 def _validate_classification(self, type, definition):
326 # only legacy_flow_classifier Type is supported at this point
327 return True
328 # TODO(igordcard): this method should be an abstract method of an
329 # abstract Classification class to be implemented by the specific
330 # Types. Also, abstract vimconnector should call the validation
331 # method before the implemented VIM connectors are called.
332
tiernoae4a8d12016-07-08 12:30:39 +0200333 def _format_exception(self, exception):
334 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
335 if isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
tierno8e995ce2016-09-22 08:13:00 +0000336 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed
337 )):
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000338 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
339 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
tiernoae4a8d12016-07-08 12:30:39 +0200340 neExceptions.NeutronException, nvExceptions.BadRequest)):
341 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + str(exception))
342 elif isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound)):
343 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + str(exception))
344 elif isinstance(exception, nvExceptions.Conflict):
345 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + str(exception))
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200346 elif isinstance(exception, vimconn.vimconnException):
347 raise
tiernof716aea2017-06-21 18:01:40 +0200348 else: # ()
tiernob84cbdc2017-07-07 14:30:30 +0200349 self.logger.error("General Exception " + str(exception), exc_info=True)
tiernoae4a8d12016-07-08 12:30:39 +0200350 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + str(exception))
351
352 def get_tenant_list(self, filter_dict={}):
353 '''Obtain tenants of VIM
354 filter_dict can contain the following keys:
355 name: filter by tenant name
356 id: filter by tenant uuid/id
357 <other VIM specific>
358 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
359 '''
ahmadsa95baa272016-11-30 09:14:11 +0500360 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
tiernoae4a8d12016-07-08 12:30:39 +0200361 try:
362 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200363 if self.api_version3:
364 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
ahmadsa95baa272016-11-30 09:14:11 +0500365 else:
tiernof716aea2017-06-21 18:01:40 +0200366 project_class_list = self.keystone.tenants.findall(**filter_dict)
ahmadsa95baa272016-11-30 09:14:11 +0500367 project_list=[]
368 for project in project_class_list:
tiernof716aea2017-06-21 18:01:40 +0200369 if filter_dict.get('id') and filter_dict["id"] != project.id:
370 continue
ahmadsa95baa272016-11-30 09:14:11 +0500371 project_list.append(project.to_dict())
372 return project_list
tiernof716aea2017-06-21 18:01:40 +0200373 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200374 self._format_exception(e)
375
376 def new_tenant(self, tenant_name, tenant_description):
377 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
378 self.logger.debug("Adding a new tenant name: %s", tenant_name)
379 try:
380 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200381 if self.api_version3:
382 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
383 description=tenant_description, is_domain=False)
ahmadsa95baa272016-11-30 09:14:11 +0500384 else:
tiernof716aea2017-06-21 18:01:40 +0200385 project = self.keystone.tenants.create(tenant_name, tenant_description)
ahmadsa95baa272016-11-30 09:14:11 +0500386 return project.id
tierno8e995ce2016-09-22 08:13:00 +0000387 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200388 self._format_exception(e)
389
390 def delete_tenant(self, tenant_id):
391 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
392 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
393 try:
394 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200395 if self.api_version3:
ahmadsa95baa272016-11-30 09:14:11 +0500396 self.keystone.projects.delete(tenant_id)
397 else:
398 self.keystone.tenants.delete(tenant_id)
tiernoae4a8d12016-07-08 12:30:39 +0200399 return tenant_id
tierno8e995ce2016-09-22 08:13:00 +0000400 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200401 self._format_exception(e)
ahmadsa95baa272016-11-30 09:14:11 +0500402
garciadeblas9f8456e2016-09-05 05:02:59 +0200403 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernoae4a8d12016-07-08 12:30:39 +0200404 '''Adds a tenant network to VIM. Returns the network identifier'''
405 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
garciadeblasedca7b32016-09-29 14:01:52 +0000406 #self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
tierno7edb6752016-03-21 17:37:52 +0100407 try:
garciadeblasedca7b32016-09-29 14:01:52 +0000408 new_net = None
tierno7edb6752016-03-21 17:37:52 +0100409 self._reload_connection()
410 network_dict = {'name': net_name, 'admin_state_up': True}
411 if net_type=="data" or net_type=="ptp":
412 if self.config.get('dataplane_physical_net') == None:
tiernoae4a8d12016-07-08 12:30:39 +0200413 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
tierno7edb6752016-03-21 17:37:52 +0100414 network_dict["provider:physical_network"] = self.config['dataplane_physical_net'] #"physnet_sriov" #TODO physical
415 network_dict["provider:network_type"] = "vlan"
416 if vlan!=None:
417 network_dict["provider:network_type"] = vlan
kate721d79b2017-06-24 04:21:38 -0700418
419 ####### VIO Specific Changes #########
420 if self.vim_type == "VIO":
421 if vlan is not None:
422 network_dict["provider:segmentation_id"] = vlan
423 else:
424 if self.config.get('dataplane_net_vlan_range') is None:
425 raise vimconn.vimconnConflictException("You must provide "\
426 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
427 "at config value before creating sriov network with vlan tag")
428
429 network_dict["provider:segmentation_id"] = self._genrate_vlanID()
430
tiernoae4a8d12016-07-08 12:30:39 +0200431 network_dict["shared"]=shared
tierno7edb6752016-03-21 17:37:52 +0100432 new_net=self.neutron.create_network({'network':network_dict})
433 #print new_net
garciadeblas9f8456e2016-09-05 05:02:59 +0200434 #create subnetwork, even if there is no profile
435 if not ip_profile:
436 ip_profile = {}
437 if 'subnet_address' not in ip_profile:
garciadeblas2299e3b2017-01-26 14:35:55 +0000438 #Fake subnet is required
439 subnet_rand = random.randint(0, 255)
440 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000441 if 'ip_version' not in ip_profile:
garciadeblas9f8456e2016-09-05 05:02:59 +0200442 ip_profile['ip_version'] = "IPv4"
tiernoa1fb4462017-06-30 12:25:50 +0200443 subnet = {"name":net_name+"-subnet",
tierno7edb6752016-03-21 17:37:52 +0100444 "network_id": new_net["network"]["id"],
garciadeblas9f8456e2016-09-05 05:02:59 +0200445 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
446 "cidr": ip_profile['subnet_address']
tierno7edb6752016-03-21 17:37:52 +0100447 }
tiernoa1fb4462017-06-30 12:25:50 +0200448 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
449 subnet['gateway_ip'] = ip_profile.get('gateway_address')
garciadeblasedca7b32016-09-29 14:01:52 +0000450 if ip_profile.get('dns_address'):
tierno455612d2017-05-30 16:40:10 +0200451 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
garciadeblas9f8456e2016-09-05 05:02:59 +0200452 if 'dhcp_enabled' in ip_profile:
453 subnet['enable_dhcp'] = False if ip_profile['dhcp_enabled']=="false" else True
454 if 'dhcp_start_address' in ip_profile:
tiernoa1fb4462017-06-30 12:25:50 +0200455 subnet['allocation_pools'] = []
garciadeblas9f8456e2016-09-05 05:02:59 +0200456 subnet['allocation_pools'].append(dict())
457 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
458 if 'dhcp_count' in ip_profile:
459 #parts = ip_profile['dhcp_start_address'].split('.')
460 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
461 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
garciadeblas21d795b2016-09-29 17:31:46 +0200462 ip_int += ip_profile['dhcp_count'] - 1
garciadeblas9f8456e2016-09-05 05:02:59 +0200463 ip_str = str(netaddr.IPAddress(ip_int))
464 subnet['allocation_pools'][0]['end'] = ip_str
garciadeblasedca7b32016-09-29 14:01:52 +0000465 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
tierno7edb6752016-03-21 17:37:52 +0100466 self.neutron.create_subnet({"subnet": subnet} )
tiernoae4a8d12016-07-08 12:30:39 +0200467 return new_net["network"]["id"]
tierno8e995ce2016-09-22 08:13:00 +0000468 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
garciadeblasedca7b32016-09-29 14:01:52 +0000469 if new_net:
470 self.neutron.delete_network(new_net['network']['id'])
tiernoae4a8d12016-07-08 12:30:39 +0200471 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100472
473 def get_network_list(self, filter_dict={}):
474 '''Obtain tenant networks of VIM
475 Filter_dict can be:
476 name: network name
477 id: network uuid
478 shared: boolean
479 tenant_id: tenant
480 admin_state_up: boolean
481 status: 'ACTIVE'
482 Returns the network list of dictionaries
483 '''
tiernoae4a8d12016-07-08 12:30:39 +0200484 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
tierno7edb6752016-03-21 17:37:52 +0100485 try:
486 self._reload_connection()
tiernof716aea2017-06-21 18:01:40 +0200487 if self.api_version3 and "tenant_id" in filter_dict:
488 filter_dict['project_id'] = filter_dict.pop('tenant_id') #TODO check
tierno7edb6752016-03-21 17:37:52 +0100489 net_dict=self.neutron.list_networks(**filter_dict)
490 net_list=net_dict["networks"]
491 self.__net_os2mano(net_list)
tiernoae4a8d12016-07-08 12:30:39 +0200492 return net_list
tierno8e995ce2016-09-22 08:13:00 +0000493 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200494 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100495
tiernoae4a8d12016-07-08 12:30:39 +0200496 def get_network(self, net_id):
497 '''Obtain details of network from VIM
498 Returns the network information from a network id'''
499 self.logger.debug(" Getting tenant network %s from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100500 filter_dict={"id": net_id}
tiernoae4a8d12016-07-08 12:30:39 +0200501 net_list = self.get_network_list(filter_dict)
tierno7edb6752016-03-21 17:37:52 +0100502 if len(net_list)==0:
tiernoae4a8d12016-07-08 12:30:39 +0200503 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
tierno7edb6752016-03-21 17:37:52 +0100504 elif len(net_list)>1:
tiernoae4a8d12016-07-08 12:30:39 +0200505 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
tierno7edb6752016-03-21 17:37:52 +0100506 net = net_list[0]
507 subnets=[]
508 for subnet_id in net.get("subnets", () ):
509 try:
510 subnet = self.neutron.show_subnet(subnet_id)
511 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200512 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
513 subnet = {"id": subnet_id, "fault": str(e)}
tierno7edb6752016-03-21 17:37:52 +0100514 subnets.append(subnet)
515 net["subnets"] = subnets
Pablo Montes Moreno51e553b2017-03-23 16:39:12 +0100516 net["encapsulation"] = net.get('provider:network_type')
Pablo Montes Moreno3fbff9b2017-03-08 11:28:15 +0100517 net["segmentation_id"] = net.get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +0200518 return net
tierno7edb6752016-03-21 17:37:52 +0100519
tiernoae4a8d12016-07-08 12:30:39 +0200520 def delete_network(self, net_id):
521 '''Deletes a tenant network from VIM. Returns the old network identifier'''
522 self.logger.debug("Deleting network '%s' from VIM", net_id)
tierno7edb6752016-03-21 17:37:52 +0100523 try:
524 self._reload_connection()
525 #delete VM ports attached to this networks before the network
526 ports = self.neutron.list_ports(network_id=net_id)
527 for p in ports['ports']:
528 try:
529 self.neutron.delete_port(p["id"])
530 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +0200531 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
tierno7edb6752016-03-21 17:37:52 +0100532 self.neutron.delete_network(net_id)
tiernoae4a8d12016-07-08 12:30:39 +0200533 return net_id
534 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
tierno8e995ce2016-09-22 08:13:00 +0000535 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200536 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100537
tiernoae4a8d12016-07-08 12:30:39 +0200538 def refresh_nets_status(self, net_list):
539 '''Get the status of the networks
540 Params: the list of network identifiers
541 Returns a dictionary with:
542 net_id: #VIM id of this network
543 status: #Mandatory. Text with one of:
544 # DELETED (not found at vim)
545 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
546 # OTHER (Vim reported other status not understood)
547 # ERROR (VIM indicates an ERROR status)
548 # ACTIVE, INACTIVE, DOWN (admin down),
549 # BUILD (on building process)
550 #
551 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
552 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
553
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000554 '''
tiernoae4a8d12016-07-08 12:30:39 +0200555 net_dict={}
556 for net_id in net_list:
557 net = {}
558 try:
559 net_vim = self.get_network(net_id)
560 if net_vim['status'] in netStatus2manoFormat:
561 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
562 else:
563 net["status"] = "OTHER"
564 net["error_msg"] = "VIM status reported " + net_vim['status']
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000565
tierno8e995ce2016-09-22 08:13:00 +0000566 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
tiernoae4a8d12016-07-08 12:30:39 +0200567 net['status'] = 'DOWN'
tierno8e995ce2016-09-22 08:13:00 +0000568 try:
569 net['vim_info'] = yaml.safe_dump(net_vim, default_flow_style=True, width=256)
570 except yaml.representer.RepresenterError:
571 net['vim_info'] = str(net_vim)
tiernoae4a8d12016-07-08 12:30:39 +0200572 if net_vim.get('fault'): #TODO
573 net['error_msg'] = str(net_vim['fault'])
574 except vimconn.vimconnNotFoundException as e:
575 self.logger.error("Exception getting net status: %s", str(e))
576 net['status'] = "DELETED"
577 net['error_msg'] = str(e)
578 except vimconn.vimconnException as e:
579 self.logger.error("Exception getting net status: %s", str(e))
580 net['status'] = "VIM_ERROR"
581 net['error_msg'] = str(e)
582 net_dict[net_id] = net
583 return net_dict
584
585 def get_flavor(self, flavor_id):
586 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
587 self.logger.debug("Getting flavor '%s'", flavor_id)
tierno7edb6752016-03-21 17:37:52 +0100588 try:
589 self._reload_connection()
590 flavor = self.nova.flavors.find(id=flavor_id)
591 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +0200592 return flavor.to_dict()
tierno8e995ce2016-09-22 08:13:00 +0000593 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200594 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100595
tiernocf157a82017-01-30 14:07:06 +0100596 def get_flavor_id_from_data(self, flavor_dict):
597 """Obtain flavor id that match the flavor description
598 Returns the flavor_id or raises a vimconnNotFoundException
tiernoe26fc7a2017-05-30 14:43:03 +0200599 flavor_dict: contains the required ram, vcpus, disk
600 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
601 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
602 vimconnNotFoundException is raised
tiernocf157a82017-01-30 14:07:06 +0100603 """
tiernoe26fc7a2017-05-30 14:43:03 +0200604 exact_match = False if self.config.get('use_existing_flavors') else True
tiernocf157a82017-01-30 14:07:06 +0100605 try:
606 self._reload_connection()
tiernoe26fc7a2017-05-30 14:43:03 +0200607 flavor_candidate_id = None
608 flavor_candidate_data = (10000, 10000, 10000)
609 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
610 # numa=None
611 numas = flavor_dict.get("extended", {}).get("numas")
tiernocf157a82017-01-30 14:07:06 +0100612 if numas:
613 #TODO
614 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemted")
615 # if len(numas) > 1:
616 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
617 # numa=numas[0]
618 # numas = extended.get("numas")
619 for flavor in self.nova.flavors.list():
620 epa = flavor.get_keys()
621 if epa:
622 continue
tiernoe26fc7a2017-05-30 14:43:03 +0200623 # TODO
624 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
625 if flavor_data == flavor_target:
626 return flavor.id
627 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
628 flavor_candidate_id = flavor.id
629 flavor_candidate_data = flavor_data
630 if not exact_match and flavor_candidate_id:
631 return flavor_candidate_id
tiernocf157a82017-01-30 14:07:06 +0100632 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
633 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
634 self._format_exception(e)
635
tiernoae4a8d12016-07-08 12:30:39 +0200636 def new_flavor(self, flavor_data, change_name_if_used=True):
tierno7edb6752016-03-21 17:37:52 +0100637 '''Adds a tenant flavor to openstack VIM
tiernoae4a8d12016-07-08 12:30:39 +0200638 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 +0100639 Returns the flavor identifier
640 '''
tiernoae4a8d12016-07-08 12:30:39 +0200641 self.logger.debug("Adding flavor '%s'", str(flavor_data))
tierno7edb6752016-03-21 17:37:52 +0100642 retry=0
tiernoae4a8d12016-07-08 12:30:39 +0200643 max_retries=3
tierno7edb6752016-03-21 17:37:52 +0100644 name_suffix = 0
tiernoae4a8d12016-07-08 12:30:39 +0200645 name=flavor_data['name']
646 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100647 retry+=1
648 try:
649 self._reload_connection()
650 if change_name_if_used:
651 #get used names
652 fl_names=[]
653 fl=self.nova.flavors.list()
654 for f in fl:
655 fl_names.append(f.name)
656 while name in fl_names:
657 name_suffix += 1
tiernoae4a8d12016-07-08 12:30:39 +0200658 name = flavor_data['name']+"-" + str(name_suffix)
kate721d79b2017-06-24 04:21:38 -0700659
tiernoae4a8d12016-07-08 12:30:39 +0200660 ram = flavor_data.get('ram',64)
661 vcpus = flavor_data.get('vcpus',1)
tierno7edb6752016-03-21 17:37:52 +0100662 numa_properties=None
663
tiernoae4a8d12016-07-08 12:30:39 +0200664 extended = flavor_data.get("extended")
tierno7edb6752016-03-21 17:37:52 +0100665 if extended:
666 numas=extended.get("numas")
667 if numas:
668 numa_nodes = len(numas)
669 if numa_nodes > 1:
670 return -1, "Can not add flavor with more than one numa"
671 numa_properties = {"hw:numa_nodes":str(numa_nodes)}
672 numa_properties["hw:mem_page_size"] = "large"
673 numa_properties["hw:cpu_policy"] = "dedicated"
674 numa_properties["hw:numa_mempolicy"] = "strict"
kate721d79b2017-06-24 04:21:38 -0700675 if self.vim_type == "VIO":
676 numa_properties["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
677 numa_properties["vmware:latency_sensitivity_level"] = "high"
tierno7edb6752016-03-21 17:37:52 +0100678 for numa in numas:
679 #overwrite ram and vcpus
680 ram = numa['memory']*1024
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200681 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
tierno7edb6752016-03-21 17:37:52 +0100682 if 'paired-threads' in numa:
683 vcpus = numa['paired-threads']*2
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200684 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
685 numa_properties["hw:cpu_thread_policy"] = "require"
686 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100687 elif 'cores' in numa:
688 vcpus = numa['cores']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200689 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
690 numa_properties["hw:cpu_thread_policy"] = "isolate"
691 numa_properties["hw:cpu_policy"] = "dedicated"
tierno7edb6752016-03-21 17:37:52 +0100692 elif 'threads' in numa:
693 vcpus = numa['threads']
Pablo Montes Morenoea1d6232017-05-24 11:33:24 +0200694 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
695 numa_properties["hw:cpu_thread_policy"] = "prefer"
696 numa_properties["hw:cpu_policy"] = "dedicated"
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200697 # for interface in numa.get("interfaces",() ):
698 # if interface["dedicated"]=="yes":
699 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
700 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000701
tierno7edb6752016-03-21 17:37:52 +0100702 #create flavor
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000703 new_flavor=self.nova.flavors.create(name,
704 ram,
705 vcpus,
tiernoae4a8d12016-07-08 12:30:39 +0200706 flavor_data.get('disk',1),
707 is_public=flavor_data.get('is_public', True)
kate721d79b2017-06-24 04:21:38 -0700708 )
tierno7edb6752016-03-21 17:37:52 +0100709 #add metadata
710 if numa_properties:
711 new_flavor.set_keys(numa_properties)
tiernoae4a8d12016-07-08 12:30:39 +0200712 return new_flavor.id
tierno7edb6752016-03-21 17:37:52 +0100713 except nvExceptions.Conflict as e:
tiernoae4a8d12016-07-08 12:30:39 +0200714 if change_name_if_used and retry < max_retries:
tierno7edb6752016-03-21 17:37:52 +0100715 continue
tiernoae4a8d12016-07-08 12:30:39 +0200716 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100717 #except nvExceptions.BadRequest as e:
718 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200719 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100720
tiernoae4a8d12016-07-08 12:30:39 +0200721 def delete_flavor(self,flavor_id):
722 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
tierno7edb6752016-03-21 17:37:52 +0100723 '''
tiernoae4a8d12016-07-08 12:30:39 +0200724 try:
725 self._reload_connection()
726 self.nova.flavors.delete(flavor_id)
727 return flavor_id
728 #except nvExceptions.BadRequest as e:
tierno8e995ce2016-09-22 08:13:00 +0000729 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200730 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100731
tiernoae4a8d12016-07-08 12:30:39 +0200732 def new_image(self,image_dict):
tierno7edb6752016-03-21 17:37:52 +0100733 '''
tiernoae4a8d12016-07-08 12:30:39 +0200734 Adds a tenant image to VIM. imge_dict is a dictionary with:
735 name: name
736 disk_format: qcow2, vhd, vmdk, raw (by default), ...
737 location: path or URI
738 public: "yes" or "no"
739 metadata: metadata of the image
740 Returns the image_id
tierno7edb6752016-03-21 17:37:52 +0100741 '''
tiernoae4a8d12016-07-08 12:30:39 +0200742 retry=0
743 max_retries=3
744 while retry<max_retries:
tierno7edb6752016-03-21 17:37:52 +0100745 retry+=1
746 try:
747 self._reload_connection()
748 #determine format http://docs.openstack.org/developer/glance/formats.html
749 if "disk_format" in image_dict:
750 disk_format=image_dict["disk_format"]
garciadeblas14480452017-01-10 13:08:07 +0100751 else: #autodiscover based on extension
tierno7edb6752016-03-21 17:37:52 +0100752 if image_dict['location'][-6:]==".qcow2":
753 disk_format="qcow2"
754 elif image_dict['location'][-4:]==".vhd":
755 disk_format="vhd"
756 elif image_dict['location'][-5:]==".vmdk":
757 disk_format="vmdk"
758 elif image_dict['location'][-4:]==".vdi":
759 disk_format="vdi"
760 elif image_dict['location'][-4:]==".iso":
761 disk_format="iso"
762 elif image_dict['location'][-4:]==".aki":
763 disk_format="aki"
764 elif image_dict['location'][-4:]==".ari":
765 disk_format="ari"
766 elif image_dict['location'][-4:]==".ami":
767 disk_format="ami"
768 else:
769 disk_format="raw"
tiernoae4a8d12016-07-08 12:30:39 +0200770 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
tierno7edb6752016-03-21 17:37:52 +0100771 if image_dict['location'][0:4]=="http":
tiernob39d49e2017-08-02 14:02:15 +0200772 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
tierno7edb6752016-03-21 17:37:52 +0100773 container_format="bare", location=image_dict['location'], disk_format=disk_format)
774 else: #local path
775 with open(image_dict['location']) as fimage:
tiernob39d49e2017-08-02 14:02:15 +0200776 new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
tierno7edb6752016-03-21 17:37:52 +0100777 container_format="bare", data=fimage, disk_format=disk_format)
778 #insert metadata. We cannot use 'new_image.properties.setdefault'
779 #because nova and glance are "INDEPENDENT" and we are using nova for reading metadata
780 new_image_nova=self.nova.images.find(id=new_image.id)
781 new_image_nova.metadata.setdefault('location',image_dict['location'])
782 metadata_to_load = image_dict.get('metadata')
783 if metadata_to_load:
784 for k,v in yaml.load(metadata_to_load).iteritems():
785 new_image_nova.metadata.setdefault(k,v)
tiernoae4a8d12016-07-08 12:30:39 +0200786 return new_image.id
787 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
788 self._format_exception(e)
tierno8e995ce2016-09-22 08:13:00 +0000789 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200790 if retry==max_retries:
791 continue
792 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +0100793 except IOError as e: #can not open the file
tiernoae4a8d12016-07-08 12:30:39 +0200794 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
795 http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000796
tiernoae4a8d12016-07-08 12:30:39 +0200797 def delete_image(self, image_id):
798 '''Deletes a tenant image from openstack VIM. Returns the old id
tierno7edb6752016-03-21 17:37:52 +0100799 '''
tiernoae4a8d12016-07-08 12:30:39 +0200800 try:
801 self._reload_connection()
802 self.nova.images.delete(image_id)
803 return image_id
tierno8e995ce2016-09-22 08:13:00 +0000804 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e: #TODO remove
tiernoae4a8d12016-07-08 12:30:39 +0200805 self._format_exception(e)
806
807 def get_image_id_from_path(self, path):
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000808 '''Get the image id from image path in the VIM database. Returns the image_id'''
tiernoae4a8d12016-07-08 12:30:39 +0200809 try:
810 self._reload_connection()
811 images = self.nova.images.list()
812 for image in images:
813 if image.metadata.get("location")==path:
814 return image.id
815 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
tierno8e995ce2016-09-22 08:13:00 +0000816 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +0200817 self._format_exception(e)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +0000818
garciadeblasb69fa9f2016-09-28 12:04:10 +0200819 def get_image_list(self, filter_dict={}):
820 '''Obtain tenant images from VIM
821 Filter_dict can be:
822 id: image id
823 name: image name
824 checksum: image checksum
825 Returns the image list of dictionaries:
826 [{<the fields at Filter_dict plus some VIM specific>}, ...]
827 List can be empty
828 '''
829 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
830 try:
831 self._reload_connection()
832 filter_dict_os=filter_dict.copy()
833 #First we filter by the available filter fields: name, id. The others are removed.
834 filter_dict_os.pop('checksum',None)
835 image_list=self.nova.images.findall(**filter_dict_os)
836 if len(image_list)==0:
837 return []
838 #Then we filter by the rest of filter fields: checksum
839 filtered_list = []
840 for image in image_list:
tierno4540ea52017-01-18 17:44:32 +0100841 image_class=self.glance.images.get(image.id)
842 if 'checksum' not in filter_dict or image_class['checksum']==filter_dict.get('checksum'):
843 filtered_list.append(image_class.copy())
garciadeblasb69fa9f2016-09-28 12:04:10 +0200844 return filtered_list
845 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
846 self._format_exception(e)
847
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200848 def __wait_for_vm(self, vm_id, status):
849 """wait until vm is in the desired status and return True.
850 If the VM gets in ERROR status, return false.
851 If the timeout is reached generate an exception"""
852 elapsed_time = 0
853 while elapsed_time < server_timeout:
854 vm_status = self.nova.servers.get(vm_id).status
855 if vm_status == status:
856 return True
857 if vm_status == 'ERROR':
858 return False
859 time.sleep(1)
860 elapsed_time += 1
861
862 # if we exceeded the timeout rollback
863 if elapsed_time >= server_timeout:
864 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
865 http_code=vimconn.HTTP_Request_Timeout)
866
mirabal29356312017-07-27 12:21:22 +0200867 def _get_openstack_availablity_zones(self):
868 """
869 Get from openstack availability zones available
870 :return:
871 """
872 try:
873 openstack_availability_zone = self.nova.availability_zones.list()
874 openstack_availability_zone = [str(zone.zoneName) for zone in openstack_availability_zone
875 if zone.zoneName != 'internal']
876 return openstack_availability_zone
877 except Exception as e:
878 return None
879
880 def _set_availablity_zones(self):
881 """
882 Set vim availablity zone
883 :return:
884 """
885
886 if 'availability_zone' in self.config:
887 vim_availability_zones = self.config.get('availability_zone')
888 if isinstance(vim_availability_zones, str):
889 self.availability_zone = [vim_availability_zones]
890 elif isinstance(vim_availability_zones, list):
891 self.availability_zone = vim_availability_zones
892 else:
893 self.availability_zone = self._get_openstack_availablity_zones()
894
tierno5a3273c2017-08-29 11:43:46 +0200895 def _get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
mirabal29356312017-07-27 12:21:22 +0200896 """
tierno5a3273c2017-08-29 11:43:46 +0200897 Return thge availability zone to be used by the created VM.
898 :return: The VIM availability zone to be used or None
mirabal29356312017-07-27 12:21:22 +0200899 """
tierno5a3273c2017-08-29 11:43:46 +0200900 if availability_zone_index is None:
901 if not self.config.get('availability_zone'):
902 return None
903 elif isinstance(self.config.get('availability_zone'), str):
904 return self.config['availability_zone']
905 else:
906 # TODO consider using a different parameter at config for default AV and AV list match
907 return self.config['availability_zone'][0]
mirabal29356312017-07-27 12:21:22 +0200908
tierno5a3273c2017-08-29 11:43:46 +0200909 vim_availability_zones = self.availability_zone
910 # check if VIM offer enough availability zones describe in the VNFD
911 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
912 # check if all the names of NFV AV match VIM AV names
913 match_by_index = False
914 for av in availability_zone_list:
915 if av not in vim_availability_zones:
916 match_by_index = True
917 break
918 if match_by_index:
919 return vim_availability_zones[availability_zone_index]
920 else:
921 return availability_zone_list[availability_zone_index]
mirabal29356312017-07-27 12:21:22 +0200922 else:
tierno5a3273c2017-08-29 11:43:46 +0200923 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
mirabal29356312017-07-27 12:21:22 +0200924
tierno5a3273c2017-08-29 11:43:46 +0200925 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
926 availability_zone_index=None, availability_zone_list=None):
tierno7edb6752016-03-21 17:37:52 +0100927 '''Adds a VM instance to VIM
928 Params:
929 start: indicates if VM must start or boot in pause mode. Ignored
930 image_id,flavor_id: iamge and flavor uuid
931 net_list: list of interfaces, each one is a dictionary with:
932 name:
933 net_id: network uuid to connect
934 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
935 model: interface model, ignored #TODO
936 mac_address: used for SR-IOV ifaces #TODO for other types
937 use: 'data', 'bridge', 'mgmt'
938 type: 'virtual', 'PF', 'VF', 'VFnotShared'
939 vim_id: filled/added by this function
ahmadsaf853d452016-12-22 11:33:47 +0500940 floating_ip: True/False (or it can be None)
mirabal29356312017-07-27 12:21:22 +0200941 'cloud_config': (optional) dictionary with:
942 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
943 'users': (optional) list of users to be inserted, each item is a dict with:
944 'name': (mandatory) user name,
945 'key-pairs': (optional) list of strings with the public key to be inserted to the user
946 'user-data': (optional) string is a text script to be passed directly to cloud-init
947 'config-files': (optional). List of files to be transferred. Each item is a dict with:
948 'dest': (mandatory) string with the destination absolute path
949 'encoding': (optional, by default text). Can be one of:
950 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
951 'content' (mandatory): string with the content of the file
952 'permissions': (optional) string with file permissions, typically octal notation '0644'
953 'owner': (optional) file owner, string with the format 'owner:group'
954 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
955 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
956 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
957 'size': (mandatory) string with the size of the disk in GB
tierno5a3273c2017-08-29 11:43:46 +0200958 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
959 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
960 availability_zone_index is None
tierno7edb6752016-03-21 17:37:52 +0100961 #TODO ip, security groups
tiernoae4a8d12016-07-08 12:30:39 +0200962 Returns the instance identifier
tierno7edb6752016-03-21 17:37:52 +0100963 '''
tiernofa51c202017-01-27 14:58:17 +0100964 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 +0100965 try:
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200966 server = None
tierno6e116232016-07-18 13:01:40 +0200967 metadata={}
tierno7edb6752016-03-21 17:37:52 +0100968 net_list_vim=[]
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200969 external_network=[] # list of external networks to be connected to instance, later on used to create floating_ip
970 no_secured_ports = [] # List of port-is with port-security disabled
tierno7edb6752016-03-21 17:37:52 +0100971 self._reload_connection()
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +0200972 metadata_vpci={} # For a specific neutron plugin
tiernob84cbdc2017-07-07 14:30:30 +0200973 block_device_mapping = None
tierno7edb6752016-03-21 17:37:52 +0100974 for net in net_list:
975 if not net.get("net_id"): #skip non connected iface
976 continue
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200977
978 port_dict={
979 "network_id": net["net_id"],
980 "name": net.get("name"),
981 "admin_state_up": True
982 }
983 if net["type"]=="virtual":
984 if "vpci" in net:
985 metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
986 elif net["type"]=="VF": # for VF
987 if "vpci" in net:
988 if "VF" not in metadata_vpci:
989 metadata_vpci["VF"]=[]
990 metadata_vpci["VF"].append([ net["vpci"], "" ])
991 port_dict["binding:vnic_type"]="direct"
kate721d79b2017-06-24 04:21:38 -0700992 ########## VIO specific Changes #######
993 if self.vim_type == "VIO":
994 #Need to create port with port_security_enabled = False and no-security-groups
995 port_dict["port_security_enabled"]=False
996 port_dict["provider_security_groups"]=[]
997 port_dict["security_groups"]=[]
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +0200998 else: #For PT
kate721d79b2017-06-24 04:21:38 -0700999 ########## VIO specific Changes #######
1000 #Current VIO release does not support port with type 'direct-physical'
1001 #So no need to create virtual port in case of PCI-device.
1002 #Will update port_dict code when support gets added in next VIO release
1003 if self.vim_type == "VIO":
1004 raise vimconn.vimconnNotSupportedException("Current VIO release does not support full passthrough (PT)")
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001005 if "vpci" in net:
1006 if "PF" not in metadata_vpci:
1007 metadata_vpci["PF"]=[]
1008 metadata_vpci["PF"].append([ net["vpci"], "" ])
1009 port_dict["binding:vnic_type"]="direct-physical"
1010 if not port_dict["name"]:
1011 port_dict["name"]=name
1012 if net.get("mac_address"):
1013 port_dict["mac_address"]=net["mac_address"]
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001014 new_port = self.neutron.create_port({"port": port_dict })
1015 net["mac_adress"] = new_port["port"]["mac_address"]
1016 net["vim_id"] = new_port["port"]["id"]
tiernob84cbdc2017-07-07 14:30:30 +02001017 # if try to use a network without subnetwork, it will return a emtpy list
1018 fixed_ips = new_port["port"].get("fixed_ips")
1019 if fixed_ips:
1020 net["ip"] = fixed_ips[0].get("ip_address")
1021 else:
1022 net["ip"] = None
montesmoreno994a29d2017-08-22 11:23:06 +02001023
1024 port = {"port-id": new_port["port"]["id"]}
1025 if float(self.nova.api_version.get_string()) >= 2.32:
1026 port["tag"] = new_port["port"]["name"]
1027 net_list_vim.append(port)
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001028
ahmadsaf853d452016-12-22 11:33:47 +05001029 if net.get('floating_ip', False):
tiernof8383b82017-01-18 15:49:48 +01001030 net['exit_on_floating_ip_error'] = True
ahmadsaf853d452016-12-22 11:33:47 +05001031 external_network.append(net)
tiernof8383b82017-01-18 15:49:48 +01001032 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
1033 net['exit_on_floating_ip_error'] = False
1034 external_network.append(net)
1035
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001036 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1037 # As a workaround we wait until the VM is active and then disable the port-security
1038 if net.get("port_security") == False:
1039 no_secured_ports.append(new_port["port"]["id"])
1040
tierno7edb6752016-03-21 17:37:52 +01001041 if metadata_vpci:
1042 metadata = {"pci_assignement": json.dumps(metadata_vpci)}
tiernoafbced42016-07-23 01:43:53 +02001043 if len(metadata["pci_assignement"]) >255:
tierno6e116232016-07-18 13:01:40 +02001044 #limit the metadata size
1045 #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1046 self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
1047 metadata = {}
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001048
tiernoae4a8d12016-07-08 12:30:39 +02001049 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s' metadata %s",
1050 name, image_id, flavor_id, str(net_list_vim), description, str(metadata))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001051
tierno7edb6752016-03-21 17:37:52 +01001052 security_groups = self.config.get('security_groups')
1053 if type(security_groups) is str:
1054 security_groups = ( security_groups, )
tierno36c0b172017-01-12 18:32:28 +01001055 #cloud config
tierno0a1437e2017-10-02 00:17:43 +02001056 config_drive, userdata = self._create_user_data(cloud_config)
montesmoreno0c8def02016-12-22 12:16:23 +00001057
1058 #Create additional volumes in case these are present in disk_list
montesmoreno0c8def02016-12-22 12:16:23 +00001059 base_disk_index = ord('b')
1060 if disk_list != None:
tiernob84cbdc2017-07-07 14:30:30 +02001061 block_device_mapping = {}
montesmoreno0c8def02016-12-22 12:16:23 +00001062 for disk in disk_list:
1063 if 'image_id' in disk:
1064 volume = self.cinder.volumes.create(size = disk['size'],name = name + '_vd' +
1065 chr(base_disk_index), imageRef = disk['image_id'])
1066 else:
1067 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1068 chr(base_disk_index))
1069 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
1070 base_disk_index += 1
1071
1072 #wait until volumes are with status available
1073 keep_waiting = True
1074 elapsed_time = 0
1075 while keep_waiting and elapsed_time < volume_timeout:
1076 keep_waiting = False
1077 for volume_id in block_device_mapping.itervalues():
1078 if self.cinder.volumes.get(volume_id).status != 'available':
1079 keep_waiting = True
1080 if keep_waiting:
1081 time.sleep(1)
1082 elapsed_time += 1
1083
1084 #if we exceeded the timeout rollback
1085 if elapsed_time >= volume_timeout:
1086 #delete the volumes we just created
1087 for volume_id in block_device_mapping.itervalues():
1088 self.cinder.volumes.delete(volume_id)
1089
1090 #delete ports we just created
1091 for net_item in net_list_vim:
1092 if 'port-id' in net_item:
montesmorenocf227142017-01-12 12:24:21 +00001093 self.neutron.delete_port(net_item['port-id'])
montesmoreno0c8def02016-12-22 12:16:23 +00001094
1095 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
1096 http_code=vimconn.HTTP_Request_Timeout)
mirabal29356312017-07-27 12:21:22 +02001097 # get availability Zone
tierno5a3273c2017-08-29 11:43:46 +02001098 vm_av_zone = self._get_vm_availability_zone(availability_zone_index, availability_zone_list)
montesmoreno0c8def02016-12-22 12:16:23 +00001099
mirabal29356312017-07-27 12:21:22 +02001100 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}, "
1101 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
1102 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim, metadata,
1103 security_groups, vm_av_zone, self.config.get('keypair'),
1104 userdata, config_drive, block_device_mapping))
tierno7edb6752016-03-21 17:37:52 +01001105 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
montesmoreno0c8def02016-12-22 12:16:23 +00001106 security_groups=security_groups,
mirabal29356312017-07-27 12:21:22 +02001107 availability_zone=vm_av_zone,
montesmoreno0c8def02016-12-22 12:16:23 +00001108 key_name=self.config.get('keypair'),
1109 userdata=userdata,
tiernob84cbdc2017-07-07 14:30:30 +02001110 config_drive=config_drive,
1111 block_device_mapping=block_device_mapping
montesmoreno0c8def02016-12-22 12:16:23 +00001112 ) # , description=description)
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001113
1114 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1115 if no_secured_ports:
1116 self.__wait_for_vm(server.id, 'ACTIVE')
1117
1118 for port_id in no_secured_ports:
1119 try:
1120 self.neutron.update_port(port_id, {"port": {"port_security_enabled": False, "security_groups": None} })
1121
1122 except Exception as e:
1123 self.logger.error("It was not possible to disable port security for port {}".format(port_id))
1124 self.delete_vminstance(server.id)
1125 raise
1126
tiernoae4a8d12016-07-08 12:30:39 +02001127 #print "DONE :-)", server
ahmadsaf853d452016-12-22 11:33:47 +05001128 pool_id = None
1129 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001130
1131 if external_network:
1132 self.__wait_for_vm(server.id, 'ACTIVE')
1133
ahmadsaf853d452016-12-22 11:33:47 +05001134 for floating_network in external_network:
tiernof8383b82017-01-18 15:49:48 +01001135 try:
tiernof8383b82017-01-18 15:49:48 +01001136 assigned = False
1137 while(assigned == False):
1138 if floating_ips:
1139 ip = floating_ips.pop(0)
1140 if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id:
1141 free_floating_ip = ip.get("floating_ip_address")
1142 try:
1143 fix_ip = floating_network.get('ip')
1144 server.add_floating_ip(free_floating_ip, fix_ip)
1145 assigned = True
1146 except Exception as e:
1147 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
1148 else:
1149 #Find the external network
1150 external_nets = list()
1151 for net in self.neutron.list_networks()['networks']:
1152 if net['router:external']:
1153 external_nets.append(net)
1154
1155 if len(external_nets) == 0:
1156 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
1157 "network is present",
1158 http_code=vimconn.HTTP_Conflict)
1159 if len(external_nets) > 1:
1160 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
1161 "external networks are present",
1162 http_code=vimconn.HTTP_Conflict)
1163
1164 pool_id = external_nets[0].get('id')
1165 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
ahmadsaf853d452016-12-22 11:33:47 +05001166 try:
tiernof8383b82017-01-18 15:49:48 +01001167 #self.logger.debug("Creating floating IP")
1168 new_floating_ip = self.neutron.create_floatingip(param)
1169 free_floating_ip = new_floating_ip['floatingip']['floating_ip_address']
ahmadsaf853d452016-12-22 11:33:47 +05001170 fix_ip = floating_network.get('ip')
1171 server.add_floating_ip(free_floating_ip, fix_ip)
tiernof8383b82017-01-18 15:49:48 +01001172 assigned=True
ahmadsaf853d452016-12-22 11:33:47 +05001173 except Exception as e:
tiernof8383b82017-01-18 15:49:48 +01001174 raise vimconn.vimconnException(type(e).__name__ + ": Cannot assign floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict)
1175 except Exception as e:
1176 if not floating_network['exit_on_floating_ip_error']:
1177 self.logger.warn("Cannot create floating_ip. %s", str(e))
1178 continue
tiernof8383b82017-01-18 15:49:48 +01001179 raise
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001180
tiernoae4a8d12016-07-08 12:30:39 +02001181 return server.id
tierno7edb6752016-03-21 17:37:52 +01001182# except nvExceptions.NotFound as e:
1183# error_value=-vimconn.HTTP_Not_Found
1184# error_text= "vm instance %s not found" % vm_id
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001185# except TypeError as e:
1186# raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1187
1188 except Exception as e:
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001189 # delete the volumes we just created
tiernob84cbdc2017-07-07 14:30:30 +02001190 if block_device_mapping:
montesmoreno2a1fc4e2017-01-09 16:46:04 +00001191 for volume_id in block_device_mapping.itervalues():
1192 self.cinder.volumes.delete(volume_id)
1193
Pablo Montes Moreno6a7785b2017-07-03 10:44:30 +02001194 # Delete the VM
1195 if server != None:
1196 self.delete_vminstance(server.id)
1197 else:
1198 # delete ports we just created
1199 for net_item in net_list_vim:
1200 if 'port-id' in net_item:
1201 self.neutron.delete_port(net_item['port-id'])
1202
tiernoae4a8d12016-07-08 12:30:39 +02001203 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001204
tiernoae4a8d12016-07-08 12:30:39 +02001205 def get_vminstance(self,vm_id):
tierno7edb6752016-03-21 17:37:52 +01001206 '''Returns the VM instance information from VIM'''
tiernoae4a8d12016-07-08 12:30:39 +02001207 #self.logger.debug("Getting VM from VIM")
tierno7edb6752016-03-21 17:37:52 +01001208 try:
1209 self._reload_connection()
1210 server = self.nova.servers.find(id=vm_id)
1211 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
tiernoae4a8d12016-07-08 12:30:39 +02001212 return server.to_dict()
tierno8e995ce2016-09-22 08:13:00 +00001213 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001214 self._format_exception(e)
1215
1216 def get_vminstance_console(self,vm_id, console_type="vnc"):
tierno7edb6752016-03-21 17:37:52 +01001217 '''
1218 Get a console for the virtual machine
1219 Params:
1220 vm_id: uuid of the VM
1221 console_type, can be:
1222 "novnc" (by default), "xvpvnc" for VNC types,
1223 "rdp-html5" for RDP types, "spice-html5" for SPICE types
tiernoae4a8d12016-07-08 12:30:39 +02001224 Returns dict with the console parameters:
1225 protocol: ssh, ftp, http, https, ...
1226 server: usually ip address
1227 port: the http, ssh, ... port
1228 suffix: extra text, e.g. the http path and query string
tierno7edb6752016-03-21 17:37:52 +01001229 '''
tiernoae4a8d12016-07-08 12:30:39 +02001230 self.logger.debug("Getting VM CONSOLE from VIM")
tierno7edb6752016-03-21 17:37:52 +01001231 try:
1232 self._reload_connection()
1233 server = self.nova.servers.find(id=vm_id)
1234 if console_type == None or console_type == "novnc":
1235 console_dict = server.get_vnc_console("novnc")
1236 elif console_type == "xvpvnc":
1237 console_dict = server.get_vnc_console(console_type)
1238 elif console_type == "rdp-html5":
1239 console_dict = server.get_rdp_console(console_type)
1240 elif console_type == "spice-html5":
1241 console_dict = server.get_spice_console(console_type)
1242 else:
tiernoae4a8d12016-07-08 12:30:39 +02001243 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001244
tierno7edb6752016-03-21 17:37:52 +01001245 console_dict1 = console_dict.get("console")
1246 if console_dict1:
1247 console_url = console_dict1.get("url")
1248 if console_url:
1249 #parse console_url
1250 protocol_index = console_url.find("//")
1251 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1252 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1253 if protocol_index < 0 or port_index<0 or suffix_index<0:
1254 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1255 console_dict={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001256 "server": console_url[protocol_index+2:port_index],
1257 "port": console_url[port_index:suffix_index],
1258 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001259 }
1260 protocol_index += 2
tiernoae4a8d12016-07-08 12:30:39 +02001261 return console_dict
1262 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001263
tierno8e995ce2016-09-22 08:13:00 +00001264 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001265 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001266
tiernoae4a8d12016-07-08 12:30:39 +02001267 def delete_vminstance(self, vm_id):
1268 '''Removes a VM instance from VIM. Returns the old identifier
tierno7edb6752016-03-21 17:37:52 +01001269 '''
tiernoae4a8d12016-07-08 12:30:39 +02001270 #print "osconnector: Getting VM from VIM"
tierno7edb6752016-03-21 17:37:52 +01001271 try:
1272 self._reload_connection()
1273 #delete VM ports attached to this networks before the virtual machine
1274 ports = self.neutron.list_ports(device_id=vm_id)
1275 for p in ports['ports']:
1276 try:
1277 self.neutron.delete_port(p["id"])
1278 except Exception as e:
tiernoae4a8d12016-07-08 12:30:39 +02001279 self.logger.error("Error deleting port: " + type(e).__name__ + ": "+ str(e))
montesmoreno0c8def02016-12-22 12:16:23 +00001280
1281 #commented because detaching the volumes makes the servers.delete not work properly ?!?
1282 #dettach volumes attached
1283 server = self.nova.servers.get(vm_id)
1284 volumes_attached_dict = server._info['os-extended-volumes:volumes_attached']
1285 #for volume in volumes_attached_dict:
1286 # self.cinder.volumes.detach(volume['id'])
1287
tierno7edb6752016-03-21 17:37:52 +01001288 self.nova.servers.delete(vm_id)
montesmoreno0c8def02016-12-22 12:16:23 +00001289
1290 #delete volumes.
1291 #Although having detached them should have them in active status
1292 #we ensure in this loop
1293 keep_waiting = True
1294 elapsed_time = 0
1295 while keep_waiting and elapsed_time < volume_timeout:
1296 keep_waiting = False
1297 for volume in volumes_attached_dict:
1298 if self.cinder.volumes.get(volume['id']).status != 'available':
1299 keep_waiting = True
1300 else:
1301 self.cinder.volumes.delete(volume['id'])
1302 if keep_waiting:
1303 time.sleep(1)
1304 elapsed_time += 1
1305
tiernoae4a8d12016-07-08 12:30:39 +02001306 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001307 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001308 self._format_exception(e)
tierno7edb6752016-03-21 17:37:52 +01001309 #TODO insert exception vimconn.HTTP_Unauthorized
1310 #if reaching here is because an exception
tierno7edb6752016-03-21 17:37:52 +01001311
tiernoae4a8d12016-07-08 12:30:39 +02001312 def refresh_vms_status(self, vm_list):
1313 '''Get the status of the virtual machines and their interfaces/ports
1314 Params: the list of VM identifiers
1315 Returns a dictionary with:
1316 vm_id: #VIM id of this Virtual Machine
1317 status: #Mandatory. Text with one of:
1318 # DELETED (not found at vim)
1319 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1320 # OTHER (Vim reported other status not understood)
1321 # ERROR (VIM indicates an ERROR status)
1322 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1323 # CREATING (on building process), ERROR
1324 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1325 #
1326 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1327 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1328 interfaces:
1329 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1330 mac_address: #Text format XX:XX:XX:XX:XX:XX
1331 vim_net_id: #network id where this interface is connected
1332 vim_interface_id: #interface/port VIM id
1333 ip_address: #null, or text with IPv4, IPv6 address
tierno867ffe92017-03-27 12:50:34 +02001334 compute_node: #identification of compute node where PF,VF interface is allocated
1335 pci: #PCI address of the NIC that hosts the PF,VF
1336 vlan: #physical VLAN used for VF
tierno7edb6752016-03-21 17:37:52 +01001337 '''
tiernoae4a8d12016-07-08 12:30:39 +02001338 vm_dict={}
1339 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1340 for vm_id in vm_list:
1341 vm={}
1342 try:
1343 vm_vim = self.get_vminstance(vm_id)
1344 if vm_vim['status'] in vmStatus2manoFormat:
1345 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
tierno7edb6752016-03-21 17:37:52 +01001346 else:
tiernoae4a8d12016-07-08 12:30:39 +02001347 vm['status'] = "OTHER"
1348 vm['error_msg'] = "VIM status reported " + vm_vim['status']
tierno8e995ce2016-09-22 08:13:00 +00001349 try:
1350 vm['vim_info'] = yaml.safe_dump(vm_vim, default_flow_style=True, width=256)
1351 except yaml.representer.RepresenterError:
1352 vm['vim_info'] = str(vm_vim)
tiernoae4a8d12016-07-08 12:30:39 +02001353 vm["interfaces"] = []
1354 if vm_vim.get('fault'):
1355 vm['error_msg'] = str(vm_vim['fault'])
1356 #get interfaces
tierno7edb6752016-03-21 17:37:52 +01001357 try:
tiernoae4a8d12016-07-08 12:30:39 +02001358 self._reload_connection()
1359 port_dict=self.neutron.list_ports(device_id=vm_id)
1360 for port in port_dict["ports"]:
1361 interface={}
tierno8e995ce2016-09-22 08:13:00 +00001362 try:
1363 interface['vim_info'] = yaml.safe_dump(port, default_flow_style=True, width=256)
1364 except yaml.representer.RepresenterError:
1365 interface['vim_info'] = str(port)
tiernoae4a8d12016-07-08 12:30:39 +02001366 interface["mac_address"] = port.get("mac_address")
1367 interface["vim_net_id"] = port["network_id"]
1368 interface["vim_interface_id"] = port["id"]
Mike Marchetti5b9da422017-05-02 15:35:47 -04001369 # check if OS-EXT-SRV-ATTR:host is there,
1370 # in case of non-admin credentials, it will be missing
1371 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1372 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
tierno867ffe92017-03-27 12:50:34 +02001373 interface["pci"] = None
Mike Marchetti5b9da422017-05-02 15:35:47 -04001374
1375 # check if binding:profile is there,
1376 # in case of non-admin credentials, it will be missing
1377 if port.get('binding:profile'):
1378 if port['binding:profile'].get('pci_slot'):
1379 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1380 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1381 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1382 pci = port['binding:profile']['pci_slot']
1383 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1384 interface["pci"] = pci
tierno867ffe92017-03-27 12:50:34 +02001385 interface["vlan"] = None
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001386 #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 +01001387 network = self.neutron.show_network(port["network_id"])
Pablo Montes Moreno3be0b2a2017-03-30 13:22:15 +02001388 if network['network'].get('provider:network_type') == 'vlan' and \
1389 port.get("binding:vnic_type") == "direct":
tierno867ffe92017-03-27 12:50:34 +02001390 interface["vlan"] = network['network'].get('provider:segmentation_id')
tiernoae4a8d12016-07-08 12:30:39 +02001391 ips=[]
1392 #look for floating ip address
1393 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1394 if floating_ip_dict.get("floatingips"):
1395 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
tierno7edb6752016-03-21 17:37:52 +01001396
tiernoae4a8d12016-07-08 12:30:39 +02001397 for subnet in port["fixed_ips"]:
1398 ips.append(subnet["ip_address"])
1399 interface["ip_address"] = ";".join(ips)
1400 vm["interfaces"].append(interface)
1401 except Exception as e:
1402 self.logger.error("Error getting vm interface information " + type(e).__name__ + ": "+ str(e))
1403 except vimconn.vimconnNotFoundException as e:
1404 self.logger.error("Exception getting vm status: %s", str(e))
1405 vm['status'] = "DELETED"
1406 vm['error_msg'] = str(e)
1407 except vimconn.vimconnException as e:
1408 self.logger.error("Exception getting vm status: %s", str(e))
1409 vm['status'] = "VIM_ERROR"
1410 vm['error_msg'] = str(e)
1411 vm_dict[vm_id] = vm
1412 return vm_dict
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001413
tiernoae4a8d12016-07-08 12:30:39 +02001414 def action_vminstance(self, vm_id, action_dict):
tierno7edb6752016-03-21 17:37:52 +01001415 '''Send and action over a VM instance from VIM
tiernoae4a8d12016-07-08 12:30:39 +02001416 Returns the vm_id if the action was successfully sent to the VIM'''
1417 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
tierno7edb6752016-03-21 17:37:52 +01001418 try:
1419 self._reload_connection()
1420 server = self.nova.servers.find(id=vm_id)
1421 if "start" in action_dict:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001422 if action_dict["start"]=="rebuild":
tierno7edb6752016-03-21 17:37:52 +01001423 server.rebuild()
1424 else:
1425 if server.status=="PAUSED":
1426 server.unpause()
1427 elif server.status=="SUSPENDED":
1428 server.resume()
1429 elif server.status=="SHUTOFF":
1430 server.start()
1431 elif "pause" in action_dict:
1432 server.pause()
1433 elif "resume" in action_dict:
1434 server.resume()
1435 elif "shutoff" in action_dict or "shutdown" in action_dict:
1436 server.stop()
1437 elif "forceOff" in action_dict:
1438 server.stop() #TODO
1439 elif "terminate" in action_dict:
1440 server.delete()
1441 elif "createImage" in action_dict:
1442 server.create_image()
1443 #"path":path_schema,
1444 #"description":description_schema,
1445 #"name":name_schema,
1446 #"metadata":metadata_schema,
1447 #"imageRef": id_schema,
1448 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1449 elif "rebuild" in action_dict:
1450 server.rebuild(server.image['id'])
1451 elif "reboot" in action_dict:
1452 server.reboot() #reboot_type='SOFT'
1453 elif "console" in action_dict:
1454 console_type = action_dict["console"]
1455 if console_type == None or console_type == "novnc":
1456 console_dict = server.get_vnc_console("novnc")
1457 elif console_type == "xvpvnc":
1458 console_dict = server.get_vnc_console(console_type)
1459 elif console_type == "rdp-html5":
1460 console_dict = server.get_rdp_console(console_type)
1461 elif console_type == "spice-html5":
1462 console_dict = server.get_spice_console(console_type)
1463 else:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001464 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
tiernoae4a8d12016-07-08 12:30:39 +02001465 http_code=vimconn.HTTP_Bad_Request)
tierno7edb6752016-03-21 17:37:52 +01001466 try:
1467 console_url = console_dict["console"]["url"]
1468 #parse console_url
1469 protocol_index = console_url.find("//")
1470 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1471 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1472 if protocol_index < 0 or port_index<0 or suffix_index<0:
tiernoae4a8d12016-07-08 12:30:39 +02001473 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
tierno7edb6752016-03-21 17:37:52 +01001474 console_dict2={"protocol": console_url[0:protocol_index],
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001475 "server": console_url[protocol_index+2 : port_index],
1476 "port": int(console_url[port_index+1 : suffix_index]),
1477 "suffix": console_url[suffix_index+1:]
tierno7edb6752016-03-21 17:37:52 +01001478 }
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001479 return console_dict2
tiernoae4a8d12016-07-08 12:30:39 +02001480 except Exception as e:
1481 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001482
tiernoae4a8d12016-07-08 12:30:39 +02001483 return vm_id
tierno8e995ce2016-09-22 08:13:00 +00001484 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
tiernoae4a8d12016-07-08 12:30:39 +02001485 self._format_exception(e)
1486 #TODO insert exception vimconn.HTTP_Unauthorized
1487
kate721d79b2017-06-24 04:21:38 -07001488 ####### VIO Specific Changes #########
1489 def _genrate_vlanID(self):
1490 """
1491 Method to get unused vlanID
1492 Args:
1493 None
1494 Returns:
1495 vlanID
1496 """
1497 #Get used VLAN IDs
1498 usedVlanIDs = []
1499 networks = self.get_network_list()
1500 for net in networks:
1501 if net.get('provider:segmentation_id'):
1502 usedVlanIDs.append(net.get('provider:segmentation_id'))
1503 used_vlanIDs = set(usedVlanIDs)
1504
1505 #find unused VLAN ID
1506 for vlanID_range in self.config.get('dataplane_net_vlan_range'):
1507 try:
1508 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
1509 for vlanID in xrange(start_vlanid, end_vlanid + 1):
1510 if vlanID not in used_vlanIDs:
1511 return vlanID
1512 except Exception as exp:
1513 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1514 else:
1515 raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1516 " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
1517
1518
1519 def _validate_vlan_ranges(self, dataplane_net_vlan_range):
1520 """
1521 Method to validate user given vlanID ranges
1522 Args: None
1523 Returns: None
1524 """
1525 for vlanID_range in dataplane_net_vlan_range:
1526 vlan_range = vlanID_range.replace(" ", "")
1527 #validate format
1528 vlanID_pattern = r'(\d)*-(\d)*$'
1529 match_obj = re.match(vlanID_pattern, vlan_range)
1530 if not match_obj:
1531 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}.You must provide "\
1532 "'dataplane_net_vlan_range' in format [start_ID - end_ID].".format(vlanID_range))
1533
1534 start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
1535 if start_vlanid <= 0 :
1536 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1537 "Start ID can not be zero. For VLAN "\
1538 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1539 if end_vlanid > 4094 :
1540 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1541 "End VLAN ID can not be greater than 4094. For VLAN "\
1542 "networks valid IDs are 1 to 4094 ".format(vlanID_range))
1543
1544 if start_vlanid > end_vlanid:
1545 raise vimconn.vimconnConflictException("Invalid dataplane_net_vlan_range {}."\
1546 "You must provide a 'dataplane_net_vlan_range' in format start_ID - end_ID and "\
1547 "start_ID < end_ID ".format(vlanID_range))
1548
tiernoae4a8d12016-07-08 12:30:39 +02001549#NOT USED FUNCTIONS
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001550
tiernoae4a8d12016-07-08 12:30:39 +02001551 def new_external_port(self, port_data):
1552 #TODO openstack if needed
1553 '''Adds a external port to VIM'''
1554 '''Returns the port identifier'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001555 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1556
tiernoae4a8d12016-07-08 12:30:39 +02001557 def connect_port_network(self, port_id, network_id, admin=False):
1558 #TODO openstack if needed
1559 '''Connects a external port to a network'''
1560 '''Returns status code of the VIM response'''
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001561 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1562
tiernoae4a8d12016-07-08 12:30:39 +02001563 def new_user(self, user_name, user_passwd, tenant_id=None):
1564 '''Adds a new user to openstack VIM'''
1565 '''Returns the user identifier'''
1566 self.logger.debug("osconnector: Adding a new user to VIM")
1567 try:
1568 self._reload_connection()
1569 user=self.keystone.users.create(user_name, user_passwd, tenant_id=tenant_id)
1570 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1571 return user.id
1572 except ksExceptions.ConnectionError as e:
1573 error_value=-vimconn.HTTP_Bad_Request
1574 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1575 except ksExceptions.ClientException as e: #TODO remove
tierno7edb6752016-03-21 17:37:52 +01001576 error_value=-vimconn.HTTP_Bad_Request
1577 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1578 #TODO insert exception vimconn.HTTP_Unauthorized
1579 #if reaching here is because an exception
1580 if self.debug:
tiernoae4a8d12016-07-08 12:30:39 +02001581 self.logger.debug("new_user " + error_text)
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001582 return error_value, error_text
tiernoae4a8d12016-07-08 12:30:39 +02001583
1584 def delete_user(self, user_id):
1585 '''Delete a user from openstack VIM'''
1586 '''Returns the user identifier'''
1587 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001588 print("osconnector: Deleting a user from VIM")
tiernoae4a8d12016-07-08 12:30:39 +02001589 try:
1590 self._reload_connection()
1591 self.keystone.users.delete(user_id)
1592 return 1, user_id
1593 except ksExceptions.ConnectionError as e:
1594 error_value=-vimconn.HTTP_Bad_Request
1595 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1596 except ksExceptions.NotFound as e:
1597 error_value=-vimconn.HTTP_Not_Found
1598 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1599 except ksExceptions.ClientException as e: #TODO remove
1600 error_value=-vimconn.HTTP_Bad_Request
1601 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1602 #TODO insert exception vimconn.HTTP_Unauthorized
1603 #if reaching here is because an exception
1604 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001605 print("delete_tenant " + error_text)
tiernoae4a8d12016-07-08 12:30:39 +02001606 return error_value, error_text
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001607
tierno7edb6752016-03-21 17:37:52 +01001608 def get_hosts_info(self):
1609 '''Get the information of deployed hosts
1610 Returns the hosts content'''
1611 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001612 print("osconnector: Getting Host info from VIM")
tierno7edb6752016-03-21 17:37:52 +01001613 try:
1614 h_list=[]
1615 self._reload_connection()
1616 hypervisors = self.nova.hypervisors.list()
1617 for hype in hypervisors:
1618 h_list.append( hype.to_dict() )
1619 return 1, {"hosts":h_list}
1620 except nvExceptions.NotFound as e:
1621 error_value=-vimconn.HTTP_Not_Found
1622 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1623 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1624 error_value=-vimconn.HTTP_Bad_Request
1625 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1626 #TODO insert exception vimconn.HTTP_Unauthorized
1627 #if reaching here is because an exception
1628 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001629 print("get_hosts_info " + error_text)
1630 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001631
1632 def get_hosts(self, vim_tenant):
1633 '''Get the hosts and deployed instances
1634 Returns the hosts content'''
1635 r, hype_dict = self.get_hosts_info()
1636 if r<0:
1637 return r, hype_dict
1638 hypervisors = hype_dict["hosts"]
1639 try:
1640 servers = self.nova.servers.list()
1641 for hype in hypervisors:
1642 for server in servers:
1643 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1644 if 'vm' in hype:
1645 hype['vm'].append(server.id)
1646 else:
1647 hype['vm'] = [server.id]
1648 return 1, hype_dict
1649 except nvExceptions.NotFound as e:
1650 error_value=-vimconn.HTTP_Not_Found
1651 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1652 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1653 error_value=-vimconn.HTTP_Bad_Request
1654 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1655 #TODO insert exception vimconn.HTTP_Unauthorized
1656 #if reaching here is because an exception
1657 if self.debug:
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001658 print("get_hosts " + error_text)
1659 return error_value, error_text
tierno7edb6752016-03-21 17:37:52 +01001660
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001661 def new_classification(self, name, ctype, definition):
1662 self.logger.debug(
1663 'Adding a new (Traffic) Classification to VIM, named %s', name)
1664 try:
1665 new_class = None
1666 self._reload_connection()
1667 if ctype not in supportedClassificationTypes:
1668 raise vimconn.vimconnNotSupportedException(
1669 'OpenStack VIM connector doesn\'t support provided '
1670 'Classification Type {}, supported ones are: '
1671 '{}'.format(ctype, supportedClassificationTypes))
1672 if not self._validate_classification(ctype, definition):
1673 raise vimconn.vimconnException(
1674 'Incorrect Classification definition '
1675 'for the type specified.')
1676 classification_dict = definition
1677 classification_dict['name'] = name
tierno7edb6752016-03-21 17:37:52 +01001678
Igor Duarte Cardoso3cf9bcd2017-08-14 16:39:34 +00001679 new_class = self.neutron.create_flow_classifier(
1680 {'flow_classifier': classification_dict})
1681 return new_class['flow_classifier']['id']
1682 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1683 neExceptions.NeutronException, ConnectionError) as e:
1684 self.logger.error(
1685 'Creation of Classification failed.')
1686 self._format_exception(e)
1687
1688 def get_classification(self, class_id):
1689 self.logger.debug(" Getting Classification %s from VIM", class_id)
1690 filter_dict = {"id": class_id}
1691 class_list = self.get_classification_list(filter_dict)
1692 if len(class_list) == 0:
1693 raise vimconn.vimconnNotFoundException(
1694 "Classification '{}' not found".format(class_id))
1695 elif len(class_list) > 1:
1696 raise vimconn.vimconnConflictException(
1697 "Found more than one Classification with this criteria")
1698 classification = class_list[0]
1699 return classification
1700
1701 def get_classification_list(self, filter_dict={}):
1702 self.logger.debug("Getting Classifications from VIM filter: '%s'",
1703 str(filter_dict))
1704 try:
1705 self._reload_connection()
1706 if self.api_version3 and "tenant_id" in filter_dict:
1707 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1708 classification_dict = self.neutron.list_flow_classifier(
1709 **filter_dict)
1710 classification_list = classification_dict["flow_classifiers"]
1711 self.__classification_os2mano(classification_list)
1712 return classification_list
1713 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1714 neExceptions.NeutronException, ConnectionError) as e:
1715 self._format_exception(e)
1716
1717 def delete_classification(self, class_id):
1718 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
1719 try:
1720 self._reload_connection()
1721 self.neutron.delete_flow_classifier(class_id)
1722 return class_id
1723 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1724 ksExceptions.ClientException, neExceptions.NeutronException,
1725 ConnectionError) as e:
1726 self._format_exception(e)
1727
1728 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
1729 self.logger.debug(
1730 "Adding a new Service Function Instance to VIM, named '%s'", name)
1731 try:
1732 new_sfi = None
1733 self._reload_connection()
1734 correlation = None
1735 if sfc_encap:
1736 # TODO(igordc): must be changed to NSH in Queens
1737 # (MPLS is a workaround)
1738 correlation = 'mpls'
1739 if len(ingress_ports) != 1:
1740 raise vimconn.vimconnNotSupportedException(
1741 "OpenStack VIM connector can only have "
1742 "1 ingress port per SFI")
1743 if len(egress_ports) != 1:
1744 raise vimconn.vimconnNotSupportedException(
1745 "OpenStack VIM connector can only have "
1746 "1 egress port per SFI")
1747 sfi_dict = {'name': name,
1748 'ingress': ingress_ports[0],
1749 'egress': egress_ports[0],
1750 'service_function_parameters': {
1751 'correlation': correlation}}
1752 new_sfi = self.neutron.create_port_pair({'port_pair': sfi_dict})
1753 return new_sfi['port_pair']['id']
1754 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1755 neExceptions.NeutronException, ConnectionError) as e:
1756 if new_sfi:
1757 try:
1758 self.neutron.delete_port_pair_group(
1759 new_sfi['port_pair']['id'])
1760 except Exception:
1761 self.logger.error(
1762 'Creation of Service Function Instance failed, with '
1763 'subsequent deletion failure as well.')
1764 self._format_exception(e)
1765
1766 def get_sfi(self, sfi_id):
1767 self.logger.debug(
1768 'Getting Service Function Instance %s from VIM', sfi_id)
1769 filter_dict = {"id": sfi_id}
1770 sfi_list = self.get_sfi_list(filter_dict)
1771 if len(sfi_list) == 0:
1772 raise vimconn.vimconnNotFoundException(
1773 "Service Function Instance '{}' not found".format(sfi_id))
1774 elif len(sfi_list) > 1:
1775 raise vimconn.vimconnConflictException(
1776 'Found more than one Service Function Instance '
1777 'with this criteria')
1778 sfi = sfi_list[0]
1779 return sfi
1780
1781 def get_sfi_list(self, filter_dict={}):
1782 self.logger.debug("Getting Service Function Instances from "
1783 "VIM filter: '%s'", str(filter_dict))
1784 try:
1785 self._reload_connection()
1786 if self.api_version3 and "tenant_id" in filter_dict:
1787 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1788 sfi_dict = self.neutron.list_port_pair(**filter_dict)
1789 sfi_list = sfi_dict["port_pairs"]
1790 self.__sfi_os2mano(sfi_list)
1791 return sfi_list
1792 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1793 neExceptions.NeutronException, ConnectionError) as e:
1794 self._format_exception(e)
1795
1796 def delete_sfi(self, sfi_id):
1797 self.logger.debug("Deleting Service Function Instance '%s' "
1798 "from VIM", sfi_id)
1799 try:
1800 self._reload_connection()
1801 self.neutron.delete_port_pair(sfi_id)
1802 return sfi_id
1803 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1804 ksExceptions.ClientException, neExceptions.NeutronException,
1805 ConnectionError) as e:
1806 self._format_exception(e)
1807
1808 def new_sf(self, name, sfis, sfc_encap=True):
1809 self.logger.debug("Adding a new Service Function to VIM, "
1810 "named '%s'", name)
1811 try:
1812 new_sf = None
1813 self._reload_connection()
1814 correlation = None
1815 if sfc_encap:
1816 # TODO(igordc): must be changed to NSH in Queens
1817 # (MPLS is a workaround)
1818 correlation = 'mpls'
1819 for instance in sfis:
1820 sfi = self.get_sfi(instance)
1821 if sfi.get('sfc_encap') != correlation:
1822 raise vimconn.vimconnNotSupportedException(
1823 "OpenStack VIM connector requires all SFIs of the "
1824 "same SF to share the same SFC Encapsulation")
1825 sf_dict = {'name': name,
1826 'port_pairs': sfis}
1827 new_sf = self.neutron.create_port_pair_group({
1828 'port_pair_group': sf_dict})
1829 return new_sf['port_pair_group']['id']
1830 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1831 neExceptions.NeutronException, ConnectionError) as e:
1832 if new_sf:
1833 try:
1834 self.neutron.delete_port_pair_group(
1835 new_sf['port_pair_group']['id'])
1836 except Exception:
1837 self.logger.error(
1838 'Creation of Service Function failed, with '
1839 'subsequent deletion failure as well.')
1840 self._format_exception(e)
1841
1842 def get_sf(self, sf_id):
1843 self.logger.debug("Getting Service Function %s from VIM", sf_id)
1844 filter_dict = {"id": sf_id}
1845 sf_list = self.get_sf_list(filter_dict)
1846 if len(sf_list) == 0:
1847 raise vimconn.vimconnNotFoundException(
1848 "Service Function '{}' not found".format(sf_id))
1849 elif len(sf_list) > 1:
1850 raise vimconn.vimconnConflictException(
1851 "Found more than one Service Function with this criteria")
1852 sf = sf_list[0]
1853 return sf
1854
1855 def get_sf_list(self, filter_dict={}):
1856 self.logger.debug("Getting Service Function from VIM filter: '%s'",
1857 str(filter_dict))
1858 try:
1859 self._reload_connection()
1860 if self.api_version3 and "tenant_id" in filter_dict:
1861 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1862 sf_dict = self.neutron.list_port_pair_group(**filter_dict)
1863 sf_list = sf_dict["port_pair_groups"]
1864 self.__sf_os2mano(sf_list)
1865 return sf_list
1866 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1867 neExceptions.NeutronException, ConnectionError) as e:
1868 self._format_exception(e)
1869
1870 def delete_sf(self, sf_id):
1871 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
1872 try:
1873 self._reload_connection()
1874 self.neutron.delete_port_pair_group(sf_id)
1875 return sf_id
1876 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1877 ksExceptions.ClientException, neExceptions.NeutronException,
1878 ConnectionError) as e:
1879 self._format_exception(e)
1880
1881 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
1882 self.logger.debug("Adding a new Service Function Path to VIM, "
1883 "named '%s'", name)
1884 try:
1885 new_sfp = None
1886 self._reload_connection()
1887 if not sfc_encap:
1888 raise vimconn.vimconnNotSupportedException(
1889 "OpenStack VIM connector only supports "
1890 "SFC-Encapsulated chains")
1891 # TODO(igordc): must be changed to NSH in Queens
1892 # (MPLS is a workaround)
1893 correlation = 'mpls'
1894 sfp_dict = {'name': name,
1895 'flow_classifiers': classifications,
1896 'port_pair_groups': sfs,
1897 'chain_parameters': {'correlation': correlation}}
1898 if spi:
1899 sfp_dict['chain_id'] = spi
1900 new_sfp = self.neutron.create_port_chain({'port_chain': sfp_dict})
1901 return new_sfp["port_chain"]["id"]
1902 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1903 neExceptions.NeutronException, ConnectionError) as e:
1904 if new_sfp:
1905 try:
1906 self.neutron.delete_port_chain(new_sfp['port_chain']['id'])
1907 except Exception:
1908 self.logger.error(
1909 'Creation of Service Function Path failed, with '
1910 'subsequent deletion failure as well.')
1911 self._format_exception(e)
1912
1913 def get_sfp(self, sfp_id):
1914 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
1915 filter_dict = {"id": sfp_id}
1916 sfp_list = self.get_sfp_list(filter_dict)
1917 if len(sfp_list) == 0:
1918 raise vimconn.vimconnNotFoundException(
1919 "Service Function Path '{}' not found".format(sfp_id))
1920 elif len(sfp_list) > 1:
1921 raise vimconn.vimconnConflictException(
1922 "Found more than one Service Function Path with this criteria")
1923 sfp = sfp_list[0]
1924 return sfp
1925
1926 def get_sfp_list(self, filter_dict={}):
1927 self.logger.debug("Getting Service Function Paths from VIM filter: "
1928 "'%s'", str(filter_dict))
1929 try:
1930 self._reload_connection()
1931 if self.api_version3 and "tenant_id" in filter_dict:
1932 filter_dict['project_id'] = filter_dict.pop('tenant_id')
1933 sfp_dict = self.neutron.list_port_chain(**filter_dict)
1934 sfp_list = sfp_dict["port_chains"]
1935 self.__sfp_os2mano(sfp_list)
1936 return sfp_list
1937 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1938 neExceptions.NeutronException, ConnectionError) as e:
1939 self._format_exception(e)
1940
1941 def delete_sfp(self, sfp_id):
1942 self.logger.debug(
1943 "Deleting Service Function Path '%s' from VIM", sfp_id)
1944 try:
1945 self._reload_connection()
1946 self.neutron.delete_port_chain(sfp_id)
1947 return sfp_id
1948 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
1949 ksExceptions.ClientException, neExceptions.NeutronException,
1950 ConnectionError) as e:
1951 self._format_exception(e)