feature8030 move WIM connector to plugins
[osm/RO.git] / RO-VIM-openstack / osm_rovim_openstack / vimconn_openstack.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2015 Telefonica Investigacion 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
21 '''
22 osconnector implements all the methods to interact with openstack using the python-neutronclient.
23
24 For the VNF forwarding graph, The OpenStack VIM connector calls the
25 networking-sfc Neutron extension methods, whose resources are mapped
26 to the VIM connector's SFC resources as follows:
27 - Classification (OSM) -> Flow Classifier (Neutron)
28 - Service Function Instance (OSM) -> Port Pair (Neutron)
29 - Service Function (OSM) -> Port Pair Group (Neutron)
30 - Service Function Path (OSM) -> Port Chain (Neutron)
31 '''
32 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C., Eduardo Sousa"
33 __date__ = "$22-sep-2017 23:59:59$"
34
35 from osm_ro import vimconn
36 # import json
37 import logging
38 import netaddr
39 import time
40 import yaml
41 import random
42 import re
43 import copy
44 from pprint import pformat
45
46 from novaclient import client as nClient, exceptions as nvExceptions
47 from keystoneauth1.identity import v2, v3
48 from keystoneauth1 import session
49 import keystoneclient.exceptions as ksExceptions
50 import keystoneclient.v3.client as ksClient_v3
51 import keystoneclient.v2_0.client as ksClient_v2
52 from glanceclient import client as glClient
53 import glanceclient.exc as gl1Exceptions
54 from cinderclient import client as cClient
55 from http.client import HTTPException # TODO py3 check that this base exception matches python2 httplib.HTTPException
56 from neutronclient.neutron import client as neClient
57 from neutronclient.common import exceptions as neExceptions
58 from requests.exceptions import ConnectionError
59
60
61 """contain the openstack virtual machine status to openmano status"""
62 vmStatus2manoFormat={'ACTIVE':'ACTIVE',
63 'PAUSED':'PAUSED',
64 'SUSPENDED': 'SUSPENDED',
65 'SHUTOFF':'INACTIVE',
66 'BUILD':'BUILD',
67 'ERROR':'ERROR','DELETED':'DELETED'
68 }
69 netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE','BUILD':'BUILD','ERROR':'ERROR','DELETED':'DELETED'
70 }
71
72 supportedClassificationTypes = ['legacy_flow_classifier']
73
74 #global var to have a timeout creating and deleting volumes
75 volume_timeout = 600
76 server_timeout = 600
77
78
79 class SafeDumper(yaml.SafeDumper):
80 def represent_data(self, data):
81 # Openstack APIs use custom subclasses of dict and YAML safe dumper
82 # is designed to not handle that (reference issue 142 of pyyaml)
83 if isinstance(data, dict) and data.__class__ != dict:
84 # A simple solution is to convert those items back to dicts
85 data = dict(data.items())
86
87 return super(SafeDumper, self).represent_data(data)
88
89
90 class vimconnector(vimconn.vimconnector):
91 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,
92 log_level=None, config={}, persistent_info={}):
93 '''using common constructor parameters. In this case
94 'url' is the keystone authorization url,
95 'url_admin' is not use
96 '''
97 api_version = config.get('APIversion')
98 if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'):
99 raise vimconn.vimconnException("Invalid value '{}' for config:APIversion. "
100 "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version))
101 vim_type = config.get('vim_type')
102 if vim_type and vim_type not in ('vio', 'VIO'):
103 raise vimconn.vimconnException("Invalid value '{}' for config:vim_type."
104 "Allowed values are 'vio' or 'VIO'".format(vim_type))
105
106 if config.get('dataplane_net_vlan_range') is not None:
107 #validate vlan ranges provided by user
108 self._validate_vlan_ranges(config.get('dataplane_net_vlan_range'), 'dataplane_net_vlan_range')
109
110 if config.get('multisegment_vlan_range') is not None:
111 #validate vlan ranges provided by user
112 self._validate_vlan_ranges(config.get('multisegment_vlan_range'), 'multisegment_vlan_range')
113
114 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
115 config)
116
117 if self.config.get("insecure") and self.config.get("ca_cert"):
118 raise vimconn.vimconnException("options insecure and ca_cert are mutually exclusive")
119 self.verify = True
120 if self.config.get("insecure"):
121 self.verify = False
122 if self.config.get("ca_cert"):
123 self.verify = self.config.get("ca_cert")
124
125 if not url:
126 raise TypeError('url param can not be NoneType')
127 self.persistent_info = persistent_info
128 self.availability_zone = persistent_info.get('availability_zone', None)
129 self.session = persistent_info.get('session', {'reload_client': True})
130 self.my_tenant_id = self.session.get('my_tenant_id')
131 self.nova = self.session.get('nova')
132 self.neutron = self.session.get('neutron')
133 self.cinder = self.session.get('cinder')
134 self.glance = self.session.get('glance')
135 # self.glancev1 = self.session.get('glancev1')
136 self.keystone = self.session.get('keystone')
137 self.api_version3 = self.session.get('api_version3')
138 self.vim_type = self.config.get("vim_type")
139 if self.vim_type:
140 self.vim_type = self.vim_type.upper()
141 if self.config.get("use_internal_endpoint"):
142 self.endpoint_type = "internalURL"
143 else:
144 self.endpoint_type = None
145
146 self.logger = logging.getLogger('openmano.vim.openstack')
147
148 # allow security_groups to be a list or a single string
149 if isinstance(self.config.get('security_groups'), str):
150 self.config['security_groups'] = [self.config['security_groups']]
151 self.security_groups_id = None
152
153 ####### VIO Specific Changes #########
154 if self.vim_type == "VIO":
155 self.logger = logging.getLogger('openmano.vim.vio')
156
157 if log_level:
158 self.logger.setLevel( getattr(logging, log_level))
159
160 def __getitem__(self, index):
161 """Get individuals parameters.
162 Throw KeyError"""
163 if index == 'project_domain_id':
164 return self.config.get("project_domain_id")
165 elif index == 'user_domain_id':
166 return self.config.get("user_domain_id")
167 else:
168 return vimconn.vimconnector.__getitem__(self, index)
169
170 def __setitem__(self, index, value):
171 """Set individuals parameters and it is marked as dirty so to force connection reload.
172 Throw KeyError"""
173 if index == 'project_domain_id':
174 self.config["project_domain_id"] = value
175 elif index == 'user_domain_id':
176 self.config["user_domain_id"] = value
177 else:
178 vimconn.vimconnector.__setitem__(self, index, value)
179 self.session['reload_client'] = True
180
181 def serialize(self, value):
182 """Serialization of python basic types.
183
184 In the case value is not serializable a message will be logged and a
185 simple representation of the data that cannot be converted back to
186 python is returned.
187 """
188 if isinstance(value, str):
189 return value
190
191 try:
192 return yaml.dump(value, Dumper=SafeDumper,
193 default_flow_style=True, width=256)
194 except yaml.representer.RepresenterError:
195 self.logger.debug('The following entity cannot be serialized in YAML:\n\n%s\n\n', pformat(value),
196 exc_info=True)
197 return str(value)
198
199 def _reload_connection(self):
200 '''Called before any operation, it check if credentials has changed
201 Throw keystoneclient.apiclient.exceptions.AuthorizationFailure
202 '''
203 #TODO control the timing and possible token timeout, but it seams that python client does this task for us :-)
204 if self.session['reload_client']:
205 if self.config.get('APIversion'):
206 self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3'
207 else: # get from ending auth_url that end with v3 or with v2.0
208 self.api_version3 = self.url.endswith("/v3") or self.url.endswith("/v3/")
209 self.session['api_version3'] = self.api_version3
210 if self.api_version3:
211 if self.config.get('project_domain_id') or self.config.get('project_domain_name'):
212 project_domain_id_default = None
213 else:
214 project_domain_id_default = 'default'
215 if self.config.get('user_domain_id') or self.config.get('user_domain_name'):
216 user_domain_id_default = None
217 else:
218 user_domain_id_default = 'default'
219 auth = v3.Password(auth_url=self.url,
220 username=self.user,
221 password=self.passwd,
222 project_name=self.tenant_name,
223 project_id=self.tenant_id,
224 project_domain_id=self.config.get('project_domain_id', project_domain_id_default),
225 user_domain_id=self.config.get('user_domain_id', user_domain_id_default),
226 project_domain_name=self.config.get('project_domain_name'),
227 user_domain_name=self.config.get('user_domain_name'))
228 else:
229 auth = v2.Password(auth_url=self.url,
230 username=self.user,
231 password=self.passwd,
232 tenant_name=self.tenant_name,
233 tenant_id=self.tenant_id)
234 sess = session.Session(auth=auth, verify=self.verify)
235 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River Titanium cloud and StarlingX
236 region_name = self.config.get('region_name')
237 if self.api_version3:
238 self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
239 else:
240 self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type)
241 self.session['keystone'] = self.keystone
242 # In order to enable microversion functionality an explicit microversion must be specified in 'config'.
243 # This implementation approach is due to the warning message in
244 # https://developer.openstack.org/api-guide/compute/microversions.html
245 # where it is stated that microversion backwards compatibility is not guaranteed and clients should
246 # always require an specific microversion.
247 # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config
248 version = self.config.get("microversion")
249 if not version:
250 version = "2.1"
251 # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River Titanium cloud and StarlingX
252 self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
253 self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
254 self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type, region_name=region_name)
255 try:
256 self.my_tenant_id = self.session['my_tenant_id'] = sess.get_project_id()
257 except Exception as e:
258 self.logger.error("Cannot get project_id from session", exc_info=True)
259 if self.endpoint_type == "internalURL":
260 glance_service_id = self.keystone.services.list(name="glance")[0].id
261 glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url
262 else:
263 glance_endpoint = None
264 self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint)
265 # using version 1 of glance client in new_image()
266 # self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess,
267 # endpoint=glance_endpoint)
268 self.session['reload_client'] = False
269 self.persistent_info['session'] = self.session
270 # add availablity zone info inside self.persistent_info
271 self._set_availablity_zones()
272 self.persistent_info['availability_zone'] = self.availability_zone
273 self.security_groups_id = None # force to get again security_groups_ids next time they are needed
274
275 def __net_os2mano(self, net_list_dict):
276 '''Transform the net openstack format to mano format
277 net_list_dict can be a list of dict or a single dict'''
278 if type(net_list_dict) is dict:
279 net_list_=(net_list_dict,)
280 elif type(net_list_dict) is list:
281 net_list_=net_list_dict
282 else:
283 raise TypeError("param net_list_dict must be a list or a dictionary")
284 for net in net_list_:
285 if net.get('provider:network_type') == "vlan":
286 net['type']='data'
287 else:
288 net['type']='bridge'
289
290 def __classification_os2mano(self, class_list_dict):
291 """Transform the openstack format (Flow Classifier) to mano format
292 (Classification) class_list_dict can be a list of dict or a single dict
293 """
294 if isinstance(class_list_dict, dict):
295 class_list_ = [class_list_dict]
296 elif isinstance(class_list_dict, list):
297 class_list_ = class_list_dict
298 else:
299 raise TypeError(
300 "param class_list_dict must be a list or a dictionary")
301 for classification in class_list_:
302 id = classification.pop('id')
303 name = classification.pop('name')
304 description = classification.pop('description')
305 project_id = classification.pop('project_id')
306 tenant_id = classification.pop('tenant_id')
307 original_classification = copy.deepcopy(classification)
308 classification.clear()
309 classification['ctype'] = 'legacy_flow_classifier'
310 classification['definition'] = original_classification
311 classification['id'] = id
312 classification['name'] = name
313 classification['description'] = description
314 classification['project_id'] = project_id
315 classification['tenant_id'] = tenant_id
316
317 def __sfi_os2mano(self, sfi_list_dict):
318 """Transform the openstack format (Port Pair) to mano format (SFI)
319 sfi_list_dict can be a list of dict or a single dict
320 """
321 if isinstance(sfi_list_dict, dict):
322 sfi_list_ = [sfi_list_dict]
323 elif isinstance(sfi_list_dict, list):
324 sfi_list_ = sfi_list_dict
325 else:
326 raise TypeError(
327 "param sfi_list_dict must be a list or a dictionary")
328 for sfi in sfi_list_:
329 sfi['ingress_ports'] = []
330 sfi['egress_ports'] = []
331 if sfi.get('ingress'):
332 sfi['ingress_ports'].append(sfi['ingress'])
333 if sfi.get('egress'):
334 sfi['egress_ports'].append(sfi['egress'])
335 del sfi['ingress']
336 del sfi['egress']
337 params = sfi.get('service_function_parameters')
338 sfc_encap = False
339 if params:
340 correlation = params.get('correlation')
341 if correlation:
342 sfc_encap = True
343 sfi['sfc_encap'] = sfc_encap
344 del sfi['service_function_parameters']
345
346 def __sf_os2mano(self, sf_list_dict):
347 """Transform the openstack format (Port Pair Group) to mano format (SF)
348 sf_list_dict can be a list of dict or a single dict
349 """
350 if isinstance(sf_list_dict, dict):
351 sf_list_ = [sf_list_dict]
352 elif isinstance(sf_list_dict, list):
353 sf_list_ = sf_list_dict
354 else:
355 raise TypeError(
356 "param sf_list_dict must be a list or a dictionary")
357 for sf in sf_list_:
358 del sf['port_pair_group_parameters']
359 sf['sfis'] = sf['port_pairs']
360 del sf['port_pairs']
361
362 def __sfp_os2mano(self, sfp_list_dict):
363 """Transform the openstack format (Port Chain) to mano format (SFP)
364 sfp_list_dict can be a list of dict or a single dict
365 """
366 if isinstance(sfp_list_dict, dict):
367 sfp_list_ = [sfp_list_dict]
368 elif isinstance(sfp_list_dict, list):
369 sfp_list_ = sfp_list_dict
370 else:
371 raise TypeError(
372 "param sfp_list_dict must be a list or a dictionary")
373 for sfp in sfp_list_:
374 params = sfp.pop('chain_parameters')
375 sfc_encap = False
376 if params:
377 correlation = params.get('correlation')
378 if correlation:
379 sfc_encap = True
380 sfp['sfc_encap'] = sfc_encap
381 sfp['spi'] = sfp.pop('chain_id')
382 sfp['classifications'] = sfp.pop('flow_classifiers')
383 sfp['service_functions'] = sfp.pop('port_pair_groups')
384
385 # placeholder for now; read TODO note below
386 def _validate_classification(self, type, definition):
387 # only legacy_flow_classifier Type is supported at this point
388 return True
389 # TODO(igordcard): this method should be an abstract method of an
390 # abstract Classification class to be implemented by the specific
391 # Types. Also, abstract vimconnector should call the validation
392 # method before the implemented VIM connectors are called.
393
394 def _format_exception(self, exception):
395 '''Transform a keystone, nova, neutron exception into a vimconn exception'''
396
397 message_error = exception.message
398
399 if isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound, ksExceptions.NotFound,
400 gl1Exceptions.HTTPNotFound)):
401 raise vimconn.vimconnNotFoundException(type(exception).__name__ + ": " + message_error)
402 elif isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError,
403 ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed)):
404 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + message_error)
405 elif isinstance(exception, (KeyError, nvExceptions.BadRequest, ksExceptions.BadRequest)):
406 raise vimconn.vimconnException(type(exception).__name__ + ": " + message_error)
407 elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException,
408 neExceptions.NeutronException)):
409 raise vimconn.vimconnUnexpectedResponse(type(exception).__name__ + ": " + message_error)
410 elif isinstance(exception, nvExceptions.Conflict):
411 raise vimconn.vimconnConflictException(type(exception).__name__ + ": " + message_error)
412 elif isinstance(exception, vimconn.vimconnException):
413 raise exception
414 else: # ()
415 self.logger.error("General Exception " + message_error, exc_info=True)
416 raise vimconn.vimconnConnectionException(type(exception).__name__ + ": " + message_error)
417
418 def _get_ids_from_name(self):
419 """
420 Obtain ids from name of tenant and security_groups. Store at self .security_groups_id"
421 :return: None
422 """
423 # get tenant_id if only tenant_name is supplied
424 self._reload_connection()
425 if not self.my_tenant_id:
426 raise vimconn.vimconnConnectionException("Error getting tenant information from name={} id={}".
427 format(self.tenant_name, self.tenant_id))
428 if self.config.get('security_groups') and not self.security_groups_id:
429 # convert from name to id
430 neutron_sg_list = self.neutron.list_security_groups(tenant_id=self.my_tenant_id)["security_groups"]
431
432 self.security_groups_id = []
433 for sg in self.config.get('security_groups'):
434 for neutron_sg in neutron_sg_list:
435 if sg in (neutron_sg["id"], neutron_sg["name"]):
436 self.security_groups_id.append(neutron_sg["id"])
437 break
438 else:
439 self.security_groups_id = None
440 raise vimconn.vimconnConnectionException("Not found security group {} for this tenant".format(sg))
441
442 def check_vim_connectivity(self):
443 # just get network list to check connectivity and credentials
444 self.get_network_list(filter_dict={})
445
446 def get_tenant_list(self, filter_dict={}):
447 '''Obtain tenants of VIM
448 filter_dict can contain the following keys:
449 name: filter by tenant name
450 id: filter by tenant uuid/id
451 <other VIM specific>
452 Returns the tenant list of dictionaries: [{'name':'<name>, 'id':'<id>, ...}, ...]
453 '''
454 self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict))
455 try:
456 self._reload_connection()
457 if self.api_version3:
458 project_class_list = self.keystone.projects.list(name=filter_dict.get("name"))
459 else:
460 project_class_list = self.keystone.tenants.findall(**filter_dict)
461 project_list=[]
462 for project in project_class_list:
463 if filter_dict.get('id') and filter_dict["id"] != project.id:
464 continue
465 project_list.append(project.to_dict())
466 return project_list
467 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e:
468 self._format_exception(e)
469
470 def new_tenant(self, tenant_name, tenant_description):
471 '''Adds a new tenant to openstack VIM. Returns the tenant identifier'''
472 self.logger.debug("Adding a new tenant name: %s", tenant_name)
473 try:
474 self._reload_connection()
475 if self.api_version3:
476 project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"),
477 description=tenant_description, is_domain=False)
478 else:
479 project = self.keystone.tenants.create(tenant_name, tenant_description)
480 return project.id
481 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.BadRequest, ConnectionError) as e:
482 self._format_exception(e)
483
484 def delete_tenant(self, tenant_id):
485 '''Delete a tenant from openstack VIM. Returns the old tenant identifier'''
486 self.logger.debug("Deleting tenant %s from VIM", tenant_id)
487 try:
488 self._reload_connection()
489 if self.api_version3:
490 self.keystone.projects.delete(tenant_id)
491 else:
492 self.keystone.tenants.delete(tenant_id)
493 return tenant_id
494 except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.NotFound, ConnectionError) as e:
495 self._format_exception(e)
496
497 def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None):
498 """Adds a tenant network to VIM
499 Params:
500 'net_name': name of the network
501 'net_type': one of:
502 'bridge': overlay isolated network
503 'data': underlay E-LAN network for Passthrough and SRIOV interfaces
504 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces.
505 'ip_profile': is a dict containing the IP parameters of the network
506 'ip_version': can be "IPv4" or "IPv6" (Currently only IPv4 is implemented)
507 'subnet_address': ip_prefix_schema, that is X.X.X.X/Y
508 'gateway_address': (Optional) ip_schema, that is X.X.X.X
509 'dns_address': (Optional) comma separated list of ip_schema, e.g. X.X.X.X[,X,X,X,X]
510 'dhcp_enabled': True or False
511 'dhcp_start_address': ip_schema, first IP to grant
512 'dhcp_count': number of IPs to grant.
513 'shared': if this network can be seen/use by other tenants/organization
514 'vlan': in case of a data or ptp net_type, the intended vlan tag to be used for the network
515 Returns a tuple with the network identifier and created_items, or raises an exception on error
516 created_items can be None or a dictionary where this method can include key-values that will be passed to
517 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
518 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
519 as not present.
520 """
521 self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type)
522 # self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile))
523 try:
524 new_net = None
525 created_items = {}
526 self._reload_connection()
527 network_dict = {'name': net_name, 'admin_state_up': True}
528 if net_type=="data" or net_type=="ptp":
529 if self.config.get('dataplane_physical_net') == None:
530 raise vimconn.vimconnConflictException("You must provide a 'dataplane_physical_net' at config value before creating sriov network")
531 if not self.config.get('multisegment_support'):
532 network_dict["provider:physical_network"] = self.config[
533 'dataplane_physical_net'] # "physnet_sriov" #TODO physical
534 network_dict["provider:network_type"] = "vlan"
535 if vlan!=None:
536 network_dict["provider:network_type"] = vlan
537 else:
538 ###### Multi-segment case ######
539 segment_list = []
540 segment1_dict = {}
541 segment1_dict["provider:physical_network"] = ''
542 segment1_dict["provider:network_type"] = 'vxlan'
543 segment_list.append(segment1_dict)
544 segment2_dict = {}
545 segment2_dict["provider:physical_network"] = self.config['dataplane_physical_net']
546 segment2_dict["provider:network_type"] = "vlan"
547 if self.config.get('multisegment_vlan_range'):
548 vlanID = self._generate_multisegment_vlanID()
549 segment2_dict["provider:segmentation_id"] = vlanID
550 # else
551 # raise vimconn.vimconnConflictException(
552 # "You must provide 'multisegment_vlan_range' at config dict before creating a multisegment network")
553 segment_list.append(segment2_dict)
554 network_dict["segments"] = segment_list
555
556 ####### VIO Specific Changes #########
557 if self.vim_type == "VIO":
558 if vlan is not None:
559 network_dict["provider:segmentation_id"] = vlan
560 else:
561 if self.config.get('dataplane_net_vlan_range') is None:
562 raise vimconn.vimconnConflictException("You must provide "\
563 "'dataplane_net_vlan_range' in format [start_ID - end_ID]"\
564 "at config value before creating sriov network with vlan tag")
565
566 network_dict["provider:segmentation_id"] = self._generate_vlanID()
567
568 network_dict["shared"] = shared
569 if self.config.get("disable_network_port_security"):
570 network_dict["port_security_enabled"] = False
571 new_net = self.neutron.create_network({'network':network_dict})
572 # print new_net
573 # create subnetwork, even if there is no profile
574 if not ip_profile:
575 ip_profile = {}
576 if not ip_profile.get('subnet_address'):
577 #Fake subnet is required
578 subnet_rand = random.randint(0, 255)
579 ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand)
580 if 'ip_version' not in ip_profile:
581 ip_profile['ip_version'] = "IPv4"
582 subnet = {"name": net_name+"-subnet",
583 "network_id": new_net["network"]["id"],
584 "ip_version": 4 if ip_profile['ip_version']=="IPv4" else 6,
585 "cidr": ip_profile['subnet_address']
586 }
587 # Gateway should be set to None if not needed. Otherwise openstack assigns one by default
588 if ip_profile.get('gateway_address'):
589 subnet['gateway_ip'] = ip_profile['gateway_address']
590 else:
591 subnet['gateway_ip'] = None
592 if ip_profile.get('dns_address'):
593 subnet['dns_nameservers'] = ip_profile['dns_address'].split(";")
594 if 'dhcp_enabled' in ip_profile:
595 subnet['enable_dhcp'] = False if \
596 ip_profile['dhcp_enabled']=="false" or ip_profile['dhcp_enabled']==False else True
597 if ip_profile.get('dhcp_start_address'):
598 subnet['allocation_pools'] = []
599 subnet['allocation_pools'].append(dict())
600 subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address']
601 if ip_profile.get('dhcp_count'):
602 #parts = ip_profile['dhcp_start_address'].split('.')
603 #ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3])
604 ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address']))
605 ip_int += ip_profile['dhcp_count'] - 1
606 ip_str = str(netaddr.IPAddress(ip_int))
607 subnet['allocation_pools'][0]['end'] = ip_str
608 #self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet))
609 self.neutron.create_subnet({"subnet": subnet} )
610
611 if net_type == "data" and self.config.get('multisegment_support'):
612 if self.config.get('l2gw_support'):
613 l2gw_list = self.neutron.list_l2_gateways().get("l2_gateways", ())
614 for l2gw in l2gw_list:
615 l2gw_conn = {}
616 l2gw_conn["l2_gateway_id"] = l2gw["id"]
617 l2gw_conn["network_id"] = new_net["network"]["id"]
618 l2gw_conn["segmentation_id"] = str(vlanID)
619 new_l2gw_conn = self.neutron.create_l2_gateway_connection({"l2_gateway_connection": l2gw_conn})
620 created_items["l2gwconn:" + str(new_l2gw_conn["l2_gateway_connection"]["id"])] = True
621 return new_net["network"]["id"], created_items
622 except Exception as e:
623 #delete l2gw connections (if any) before deleting the network
624 for k, v in created_items.items():
625 if not v: # skip already deleted
626 continue
627 try:
628 k_item, _, k_id = k.partition(":")
629 if k_item == "l2gwconn":
630 self.neutron.delete_l2_gateway_connection(k_id)
631 except Exception as e2:
632 self.logger.error("Error deleting l2 gateway connection: {}: {}".format(type(e2).__name__, e2))
633 if new_net:
634 self.neutron.delete_network(new_net['network']['id'])
635 self._format_exception(e)
636
637 def get_network_list(self, filter_dict={}):
638 '''Obtain tenant networks of VIM
639 Filter_dict can be:
640 name: network name
641 id: network uuid
642 shared: boolean
643 tenant_id: tenant
644 admin_state_up: boolean
645 status: 'ACTIVE'
646 Returns the network list of dictionaries
647 '''
648 self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict))
649 try:
650 self._reload_connection()
651 filter_dict_os = filter_dict.copy()
652 if self.api_version3 and "tenant_id" in filter_dict_os:
653 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id') #T ODO check
654 net_dict = self.neutron.list_networks(**filter_dict_os)
655 net_list = net_dict["networks"]
656 self.__net_os2mano(net_list)
657 return net_list
658 except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
659 self._format_exception(e)
660
661 def get_network(self, net_id):
662 '''Obtain details of network from VIM
663 Returns the network information from a network id'''
664 self.logger.debug(" Getting tenant network %s from VIM", net_id)
665 filter_dict={"id": net_id}
666 net_list = self.get_network_list(filter_dict)
667 if len(net_list)==0:
668 raise vimconn.vimconnNotFoundException("Network '{}' not found".format(net_id))
669 elif len(net_list)>1:
670 raise vimconn.vimconnConflictException("Found more than one network with this criteria")
671 net = net_list[0]
672 subnets=[]
673 for subnet_id in net.get("subnets", () ):
674 try:
675 subnet = self.neutron.show_subnet(subnet_id)
676 except Exception as e:
677 self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e)))
678 subnet = {"id": subnet_id, "fault": str(e)}
679 subnets.append(subnet)
680 net["subnets"] = subnets
681 net["encapsulation"] = net.get('provider:network_type')
682 net["encapsulation_type"] = net.get('provider:network_type')
683 net["segmentation_id"] = net.get('provider:segmentation_id')
684 net["encapsulation_id"] = net.get('provider:segmentation_id')
685 return net
686
687 def delete_network(self, net_id, created_items=None):
688 """
689 Removes a tenant network from VIM and its associated elements
690 :param net_id: VIM identifier of the network, provided by method new_network
691 :param created_items: dictionary with extra items to be deleted. provided by method new_network
692 Returns the network identifier or raises an exception upon error or when network is not found
693 """
694 self.logger.debug("Deleting network '%s' from VIM", net_id)
695 if created_items == None:
696 created_items = {}
697 try:
698 self._reload_connection()
699 #delete l2gw connections (if any) before deleting the network
700 for k, v in created_items.items():
701 if not v: # skip already deleted
702 continue
703 try:
704 k_item, _, k_id = k.partition(":")
705 if k_item == "l2gwconn":
706 self.neutron.delete_l2_gateway_connection(k_id)
707 except Exception as e:
708 self.logger.error("Error deleting l2 gateway connection: {}: {}".format(type(e).__name__, e))
709 #delete VM ports attached to this networks before the network
710 ports = self.neutron.list_ports(network_id=net_id)
711 for p in ports['ports']:
712 try:
713 self.neutron.delete_port(p["id"])
714 except Exception as e:
715 self.logger.error("Error deleting port %s: %s", p["id"], str(e))
716 self.neutron.delete_network(net_id)
717 return net_id
718 except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException,
719 ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e:
720 self._format_exception(e)
721
722 def refresh_nets_status(self, net_list):
723 '''Get the status of the networks
724 Params: the list of network identifiers
725 Returns a dictionary with:
726 net_id: #VIM id of this network
727 status: #Mandatory. Text with one of:
728 # DELETED (not found at vim)
729 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
730 # OTHER (Vim reported other status not understood)
731 # ERROR (VIM indicates an ERROR status)
732 # ACTIVE, INACTIVE, DOWN (admin down),
733 # BUILD (on building process)
734 #
735 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
736 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
737
738 '''
739 net_dict={}
740 for net_id in net_list:
741 net = {}
742 try:
743 net_vim = self.get_network(net_id)
744 if net_vim['status'] in netStatus2manoFormat:
745 net["status"] = netStatus2manoFormat[ net_vim['status'] ]
746 else:
747 net["status"] = "OTHER"
748 net["error_msg"] = "VIM status reported " + net_vim['status']
749
750 if net['status'] == "ACTIVE" and not net_vim['admin_state_up']:
751 net['status'] = 'DOWN'
752
753 net['vim_info'] = self.serialize(net_vim)
754
755 if net_vim.get('fault'): #TODO
756 net['error_msg'] = str(net_vim['fault'])
757 except vimconn.vimconnNotFoundException as e:
758 self.logger.error("Exception getting net status: %s", str(e))
759 net['status'] = "DELETED"
760 net['error_msg'] = str(e)
761 except vimconn.vimconnException as e:
762 self.logger.error("Exception getting net status: %s", str(e))
763 net['status'] = "VIM_ERROR"
764 net['error_msg'] = str(e)
765 net_dict[net_id] = net
766 return net_dict
767
768 def get_flavor(self, flavor_id):
769 '''Obtain flavor details from the VIM. Returns the flavor dict details'''
770 self.logger.debug("Getting flavor '%s'", flavor_id)
771 try:
772 self._reload_connection()
773 flavor = self.nova.flavors.find(id=flavor_id)
774 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
775 return flavor.to_dict()
776 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
777 self._format_exception(e)
778
779 def get_flavor_id_from_data(self, flavor_dict):
780 """Obtain flavor id that match the flavor description
781 Returns the flavor_id or raises a vimconnNotFoundException
782 flavor_dict: contains the required ram, vcpus, disk
783 If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus
784 and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a
785 vimconnNotFoundException is raised
786 """
787 exact_match = False if self.config.get('use_existing_flavors') else True
788 try:
789 self._reload_connection()
790 flavor_candidate_id = None
791 flavor_candidate_data = (10000, 10000, 10000)
792 flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"])
793 # numa=None
794 extended = flavor_dict.get("extended", {})
795 if extended:
796 #TODO
797 raise vimconn.vimconnNotFoundException("Flavor with EPA still not implemented")
798 # if len(numas) > 1:
799 # raise vimconn.vimconnNotFoundException("Cannot find any flavor with more than one numa")
800 # numa=numas[0]
801 # numas = extended.get("numas")
802 for flavor in self.nova.flavors.list():
803 epa = flavor.get_keys()
804 if epa:
805 continue
806 # TODO
807 flavor_data = (flavor.ram, flavor.vcpus, flavor.disk)
808 if flavor_data == flavor_target:
809 return flavor.id
810 elif not exact_match and flavor_target < flavor_data < flavor_candidate_data:
811 flavor_candidate_id = flavor.id
812 flavor_candidate_data = flavor_data
813 if not exact_match and flavor_candidate_id:
814 return flavor_candidate_id
815 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
816 except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, ConnectionError) as e:
817 self._format_exception(e)
818
819 def process_resource_quota(self, quota, prefix, extra_specs):
820 """
821 :param prefix:
822 :param extra_specs:
823 :return:
824 """
825 if 'limit' in quota:
826 extra_specs["quota:" + prefix + "_limit"] = quota['limit']
827 if 'reserve' in quota:
828 extra_specs["quota:" + prefix + "_reservation"] = quota['reserve']
829 if 'shares' in quota:
830 extra_specs["quota:" + prefix + "_shares_level"] = "custom"
831 extra_specs["quota:" + prefix + "_shares_share"] = quota['shares']
832
833 def new_flavor(self, flavor_data, change_name_if_used=True):
834 '''Adds a tenant flavor to openstack VIM
835 if change_name_if_used is True, it will change name in case of conflict, because it is not supported name repetition
836 Returns the flavor identifier
837 '''
838 self.logger.debug("Adding flavor '%s'", str(flavor_data))
839 retry=0
840 max_retries=3
841 name_suffix = 0
842 try:
843 name=flavor_data['name']
844 while retry<max_retries:
845 retry+=1
846 try:
847 self._reload_connection()
848 if change_name_if_used:
849 #get used names
850 fl_names=[]
851 fl=self.nova.flavors.list()
852 for f in fl:
853 fl_names.append(f.name)
854 while name in fl_names:
855 name_suffix += 1
856 name = flavor_data['name']+"-" + str(name_suffix)
857
858 ram = flavor_data.get('ram',64)
859 vcpus = flavor_data.get('vcpus',1)
860 extra_specs={}
861
862 extended = flavor_data.get("extended")
863 if extended:
864 numas=extended.get("numas")
865 if numas:
866 numa_nodes = len(numas)
867 if numa_nodes > 1:
868 return -1, "Can not add flavor with more than one numa"
869 extra_specs["hw:numa_nodes"] = str(numa_nodes)
870 extra_specs["hw:mem_page_size"] = "large"
871 extra_specs["hw:cpu_policy"] = "dedicated"
872 extra_specs["hw:numa_mempolicy"] = "strict"
873 if self.vim_type == "VIO":
874 extra_specs["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}'
875 extra_specs["vmware:latency_sensitivity_level"] = "high"
876 for numa in numas:
877 #overwrite ram and vcpus
878 #check if key 'memory' is present in numa else use ram value at flavor
879 if 'memory' in numa:
880 ram = numa['memory']*1024
881 #See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html
882 extra_specs["hw:cpu_sockets"] = 1
883 if 'paired-threads' in numa:
884 vcpus = numa['paired-threads']*2
885 #cpu_thread_policy "require" implies that the compute node must have an STM architecture
886 extra_specs["hw:cpu_thread_policy"] = "require"
887 extra_specs["hw:cpu_policy"] = "dedicated"
888 elif 'cores' in numa:
889 vcpus = numa['cores']
890 # cpu_thread_policy "prefer" implies that the host must not have an SMT architecture, or a non-SMT architecture will be emulated
891 extra_specs["hw:cpu_thread_policy"] = "isolate"
892 extra_specs["hw:cpu_policy"] = "dedicated"
893 elif 'threads' in numa:
894 vcpus = numa['threads']
895 # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture
896 extra_specs["hw:cpu_thread_policy"] = "prefer"
897 extra_specs["hw:cpu_policy"] = "dedicated"
898 # for interface in numa.get("interfaces",() ):
899 # if interface["dedicated"]=="yes":
900 # raise vimconn.vimconnException("Passthrough interfaces are not supported for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable)
901 # #TODO, add the key 'pci_passthrough:alias"="<label at config>:<number ifaces>"' when a way to connect it is available
902 elif extended.get("cpu-quota"):
903 self.process_resource_quota(extended.get("cpu-quota"), "cpu", extra_specs)
904 if extended.get("mem-quota"):
905 self.process_resource_quota(extended.get("mem-quota"), "memory", extra_specs)
906 if extended.get("vif-quota"):
907 self.process_resource_quota(extended.get("vif-quota"), "vif", extra_specs)
908 if extended.get("disk-io-quota"):
909 self.process_resource_quota(extended.get("disk-io-quota"), "disk_io", extra_specs)
910 #create flavor
911 new_flavor=self.nova.flavors.create(name,
912 ram,
913 vcpus,
914 flavor_data.get('disk',0),
915 is_public=flavor_data.get('is_public', True)
916 )
917 #add metadata
918 if extra_specs:
919 new_flavor.set_keys(extra_specs)
920 return new_flavor.id
921 except nvExceptions.Conflict as e:
922 if change_name_if_used and retry < max_retries:
923 continue
924 self._format_exception(e)
925 #except nvExceptions.BadRequest as e:
926 except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError, KeyError) as e:
927 self._format_exception(e)
928
929 def delete_flavor(self,flavor_id):
930 '''Deletes a tenant flavor from openstack VIM. Returns the old flavor_id
931 '''
932 try:
933 self._reload_connection()
934 self.nova.flavors.delete(flavor_id)
935 return flavor_id
936 #except nvExceptions.BadRequest as e:
937 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
938 self._format_exception(e)
939
940 def new_image(self,image_dict):
941 '''
942 Adds a tenant image to VIM. imge_dict is a dictionary with:
943 name: name
944 disk_format: qcow2, vhd, vmdk, raw (by default), ...
945 location: path or URI
946 public: "yes" or "no"
947 metadata: metadata of the image
948 Returns the image_id
949 '''
950 retry=0
951 max_retries=3
952 while retry<max_retries:
953 retry+=1
954 try:
955 self._reload_connection()
956 #determine format http://docs.openstack.org/developer/glance/formats.html
957 if "disk_format" in image_dict:
958 disk_format=image_dict["disk_format"]
959 else: #autodiscover based on extension
960 if image_dict['location'].endswith(".qcow2"):
961 disk_format="qcow2"
962 elif image_dict['location'].endswith(".vhd"):
963 disk_format="vhd"
964 elif image_dict['location'].endswith(".vmdk"):
965 disk_format="vmdk"
966 elif image_dict['location'].endswith(".vdi"):
967 disk_format="vdi"
968 elif image_dict['location'].endswith(".iso"):
969 disk_format="iso"
970 elif image_dict['location'].endswith(".aki"):
971 disk_format="aki"
972 elif image_dict['location'].endswith(".ari"):
973 disk_format="ari"
974 elif image_dict['location'].endswith(".ami"):
975 disk_format="ami"
976 else:
977 disk_format="raw"
978 self.logger.debug("new_image: '%s' loading from '%s'", image_dict['name'], image_dict['location'])
979 if self.vim_type == "VIO":
980 container_format = "bare"
981 if 'container_format' in image_dict:
982 container_format = image_dict['container_format']
983 new_image = self.glance.images.create(name=image_dict['name'], container_format=container_format,
984 disk_format=disk_format)
985 else:
986 new_image = self.glance.images.create(name=image_dict['name'])
987 if image_dict['location'].startswith("http"):
988 # TODO there is not a method to direct download. It must be downloaded locally with requests
989 raise vimconn.vimconnNotImplemented("Cannot create image from URL")
990 else: #local path
991 with open(image_dict['location']) as fimage:
992 self.glance.images.upload(new_image.id, fimage)
993 #new_image = self.glancev1.images.create(name=image_dict['name'], is_public=image_dict.get('public',"yes")=="yes",
994 # container_format="bare", data=fimage, disk_format=disk_format)
995 metadata_to_load = image_dict.get('metadata')
996 # TODO location is a reserved word for current openstack versions. fixed for VIO please check for openstack
997 if self.vim_type == "VIO":
998 metadata_to_load['upload_location'] = image_dict['location']
999 else:
1000 metadata_to_load['location'] = image_dict['location']
1001 self.glance.images.update(new_image.id, **metadata_to_load)
1002 return new_image.id
1003 except (nvExceptions.Conflict, ksExceptions.ClientException, nvExceptions.ClientException) as e:
1004 self._format_exception(e)
1005 except (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, ConnectionError) as e:
1006 if retry==max_retries:
1007 continue
1008 self._format_exception(e)
1009 except IOError as e: #can not open the file
1010 raise vimconn.vimconnConnectionException(type(e).__name__ + ": " + str(e)+ " for " + image_dict['location'],
1011 http_code=vimconn.HTTP_Bad_Request)
1012
1013 def delete_image(self, image_id):
1014 '''Deletes a tenant image from openstack VIM. Returns the old id
1015 '''
1016 try:
1017 self._reload_connection()
1018 self.glance.images.delete(image_id)
1019 return image_id
1020 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, gl1Exceptions.HTTPNotFound, ConnectionError) as e: #TODO remove
1021 self._format_exception(e)
1022
1023 def get_image_id_from_path(self, path):
1024 '''Get the image id from image path in the VIM database. Returns the image_id'''
1025 try:
1026 self._reload_connection()
1027 images = self.glance.images.list()
1028 for image in images:
1029 if image.metadata.get("location")==path:
1030 return image.id
1031 raise vimconn.vimconnNotFoundException("image with location '{}' not found".format( path))
1032 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
1033 self._format_exception(e)
1034
1035 def get_image_list(self, filter_dict={}):
1036 '''Obtain tenant images from VIM
1037 Filter_dict can be:
1038 id: image id
1039 name: image name
1040 checksum: image checksum
1041 Returns the image list of dictionaries:
1042 [{<the fields at Filter_dict plus some VIM specific>}, ...]
1043 List can be empty
1044 '''
1045 self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
1046 try:
1047 self._reload_connection()
1048 filter_dict_os = filter_dict.copy()
1049 #First we filter by the available filter fields: name, id. The others are removed.
1050 image_list = self.glance.images.list()
1051 filtered_list = []
1052 for image in image_list:
1053 try:
1054 if filter_dict.get("name") and image["name"] != filter_dict["name"]:
1055 continue
1056 if filter_dict.get("id") and image["id"] != filter_dict["id"]:
1057 continue
1058 if filter_dict.get("checksum") and image["checksum"] != filter_dict["checksum"]:
1059 continue
1060
1061 filtered_list.append(image.copy())
1062 except gl1Exceptions.HTTPNotFound:
1063 pass
1064 return filtered_list
1065 except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
1066 self._format_exception(e)
1067
1068 def __wait_for_vm(self, vm_id, status):
1069 """wait until vm is in the desired status and return True.
1070 If the VM gets in ERROR status, return false.
1071 If the timeout is reached generate an exception"""
1072 elapsed_time = 0
1073 while elapsed_time < server_timeout:
1074 vm_status = self.nova.servers.get(vm_id).status
1075 if vm_status == status:
1076 return True
1077 if vm_status == 'ERROR':
1078 return False
1079 time.sleep(5)
1080 elapsed_time += 5
1081
1082 # if we exceeded the timeout rollback
1083 if elapsed_time >= server_timeout:
1084 raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
1085 http_code=vimconn.HTTP_Request_Timeout)
1086
1087 def _get_openstack_availablity_zones(self):
1088 """
1089 Get from openstack availability zones available
1090 :return:
1091 """
1092 try:
1093 openstack_availability_zone = self.nova.availability_zones.list()
1094 openstack_availability_zone = [str(zone.zoneName) for zone in openstack_availability_zone
1095 if zone.zoneName != 'internal']
1096 return openstack_availability_zone
1097 except Exception as e:
1098 return None
1099
1100 def _set_availablity_zones(self):
1101 """
1102 Set vim availablity zone
1103 :return:
1104 """
1105
1106 if 'availability_zone' in self.config:
1107 vim_availability_zones = self.config.get('availability_zone')
1108 if isinstance(vim_availability_zones, str):
1109 self.availability_zone = [vim_availability_zones]
1110 elif isinstance(vim_availability_zones, list):
1111 self.availability_zone = vim_availability_zones
1112 else:
1113 self.availability_zone = self._get_openstack_availablity_zones()
1114
1115 def _get_vm_availability_zone(self, availability_zone_index, availability_zone_list):
1116 """
1117 Return thge availability zone to be used by the created VM.
1118 :return: The VIM availability zone to be used or None
1119 """
1120 if availability_zone_index is None:
1121 if not self.config.get('availability_zone'):
1122 return None
1123 elif isinstance(self.config.get('availability_zone'), str):
1124 return self.config['availability_zone']
1125 else:
1126 # TODO consider using a different parameter at config for default AV and AV list match
1127 return self.config['availability_zone'][0]
1128
1129 vim_availability_zones = self.availability_zone
1130 # check if VIM offer enough availability zones describe in the VNFD
1131 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
1132 # check if all the names of NFV AV match VIM AV names
1133 match_by_index = False
1134 for av in availability_zone_list:
1135 if av not in vim_availability_zones:
1136 match_by_index = True
1137 break
1138 if match_by_index:
1139 return vim_availability_zones[availability_zone_index]
1140 else:
1141 return availability_zone_list[availability_zone_index]
1142 else:
1143 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
1144
1145 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
1146 availability_zone_index=None, availability_zone_list=None):
1147 """Adds a VM instance to VIM
1148 Params:
1149 start: indicates if VM must start or boot in pause mode. Ignored
1150 image_id,flavor_id: iamge and flavor uuid
1151 net_list: list of interfaces, each one is a dictionary with:
1152 name:
1153 net_id: network uuid to connect
1154 vpci: virtual vcpi to assign, ignored because openstack lack #TODO
1155 model: interface model, ignored #TODO
1156 mac_address: used for SR-IOV ifaces #TODO for other types
1157 use: 'data', 'bridge', 'mgmt'
1158 type: 'virtual', 'PCI-PASSTHROUGH'('PF'), 'SR-IOV'('VF'), 'VFnotShared'
1159 vim_id: filled/added by this function
1160 floating_ip: True/False (or it can be None)
1161 'cloud_config': (optional) dictionary with:
1162 'key-pairs': (optional) list of strings with the public key to be inserted to the default user
1163 'users': (optional) list of users to be inserted, each item is a dict with:
1164 'name': (mandatory) user name,
1165 'key-pairs': (optional) list of strings with the public key to be inserted to the user
1166 'user-data': (optional) string is a text script to be passed directly to cloud-init
1167 'config-files': (optional). List of files to be transferred. Each item is a dict with:
1168 'dest': (mandatory) string with the destination absolute path
1169 'encoding': (optional, by default text). Can be one of:
1170 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
1171 'content' (mandatory): string with the content of the file
1172 'permissions': (optional) string with file permissions, typically octal notation '0644'
1173 'owner': (optional) file owner, string with the format 'owner:group'
1174 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
1175 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
1176 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
1177 'size': (mandatory) string with the size of the disk in GB
1178 'vim_id' (optional) should use this existing volume id
1179 availability_zone_index: Index of availability_zone_list to use for this this VM. None if not AV required
1180 availability_zone_list: list of availability zones given by user in the VNFD descriptor. Ignore if
1181 availability_zone_index is None
1182 #TODO ip, security groups
1183 Returns a tuple with the instance identifier and created_items or raises an exception on error
1184 created_items can be None or a dictionary where this method can include key-values that will be passed to
1185 the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc.
1186 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
1187 as not present.
1188 """
1189 self.logger.debug("new_vminstance input: image='%s' flavor='%s' nics='%s'",image_id, flavor_id,str(net_list))
1190 try:
1191 server = None
1192 created_items = {}
1193 # metadata = {}
1194 net_list_vim = []
1195 external_network = [] # list of external networks to be connected to instance, later on used to create floating_ip
1196 no_secured_ports = [] # List of port-is with port-security disabled
1197 self._reload_connection()
1198 # metadata_vpci = {} # For a specific neutron plugin
1199 block_device_mapping = None
1200
1201 for net in net_list:
1202 if not net.get("net_id"): # skip non connected iface
1203 continue
1204
1205 port_dict = {
1206 "network_id": net["net_id"],
1207 "name": net.get("name"),
1208 "admin_state_up": True
1209 }
1210 if self.config.get("security_groups") and net.get("port_security") is not False and \
1211 not self.config.get("no_port_security_extension"):
1212 if not self.security_groups_id:
1213 self._get_ids_from_name()
1214 port_dict["security_groups"] = self.security_groups_id
1215
1216 if net["type"]=="virtual":
1217 pass
1218 # if "vpci" in net:
1219 # metadata_vpci[ net["net_id"] ] = [[ net["vpci"], "" ]]
1220 elif net["type"] == "VF" or net["type"] == "SR-IOV": # for VF
1221 # if "vpci" in net:
1222 # if "VF" not in metadata_vpci:
1223 # metadata_vpci["VF"]=[]
1224 # metadata_vpci["VF"].append([ net["vpci"], "" ])
1225 port_dict["binding:vnic_type"]="direct"
1226 # VIO specific Changes
1227 if self.vim_type == "VIO":
1228 # Need to create port with port_security_enabled = False and no-security-groups
1229 port_dict["port_security_enabled"]=False
1230 port_dict["provider_security_groups"]=[]
1231 port_dict["security_groups"]=[]
1232 else: # For PT PCI-PASSTHROUGH
1233 # VIO specific Changes
1234 # Current VIO release does not support port with type 'direct-physical'
1235 # So no need to create virtual port in case of PCI-device.
1236 # Will update port_dict code when support gets added in next VIO release
1237 if self.vim_type == "VIO":
1238 raise vimconn.vimconnNotSupportedException(
1239 "Current VIO release does not support full passthrough (PT)")
1240 # if "vpci" in net:
1241 # if "PF" not in metadata_vpci:
1242 # metadata_vpci["PF"]=[]
1243 # metadata_vpci["PF"].append([ net["vpci"], "" ])
1244 port_dict["binding:vnic_type"]="direct-physical"
1245 if not port_dict["name"]:
1246 port_dict["name"]=name
1247 if net.get("mac_address"):
1248 port_dict["mac_address"]=net["mac_address"]
1249 if net.get("ip_address"):
1250 port_dict["fixed_ips"] = [{'ip_address': net["ip_address"]}]
1251 # TODO add 'subnet_id': <subnet_id>
1252 new_port = self.neutron.create_port({"port": port_dict })
1253 created_items["port:" + str(new_port["port"]["id"])] = True
1254 net["mac_adress"] = new_port["port"]["mac_address"]
1255 net["vim_id"] = new_port["port"]["id"]
1256 # if try to use a network without subnetwork, it will return a emtpy list
1257 fixed_ips = new_port["port"].get("fixed_ips")
1258 if fixed_ips:
1259 net["ip"] = fixed_ips[0].get("ip_address")
1260 else:
1261 net["ip"] = None
1262
1263 port = {"port-id": new_port["port"]["id"]}
1264 if float(self.nova.api_version.get_string()) >= 2.32:
1265 port["tag"] = new_port["port"]["name"]
1266 net_list_vim.append(port)
1267
1268 if net.get('floating_ip', False):
1269 net['exit_on_floating_ip_error'] = True
1270 external_network.append(net)
1271 elif net['use'] == 'mgmt' and self.config.get('use_floating_ip'):
1272 net['exit_on_floating_ip_error'] = False
1273 external_network.append(net)
1274 net['floating_ip'] = self.config.get('use_floating_ip')
1275
1276 # If port security is disabled when the port has not yet been attached to the VM, then all vm traffic is dropped.
1277 # As a workaround we wait until the VM is active and then disable the port-security
1278 if net.get("port_security") == False and not self.config.get("no_port_security_extension"):
1279 no_secured_ports.append(new_port["port"]["id"])
1280
1281 # if metadata_vpci:
1282 # metadata = {"pci_assignement": json.dumps(metadata_vpci)}
1283 # if len(metadata["pci_assignement"]) >255:
1284 # #limit the metadata size
1285 # #metadata["pci_assignement"] = metadata["pci_assignement"][0:255]
1286 # self.logger.warn("Metadata deleted since it exceeds the expected length (255) ")
1287 # metadata = {}
1288
1289 self.logger.debug("name '%s' image_id '%s'flavor_id '%s' net_list_vim '%s' description '%s'",
1290 name, image_id, flavor_id, str(net_list_vim), description)
1291
1292 # cloud config
1293 config_drive, userdata = self._create_user_data(cloud_config)
1294
1295 # Create additional volumes in case these are present in disk_list
1296 base_disk_index = ord('b')
1297 if disk_list:
1298 block_device_mapping = {}
1299 for disk in disk_list:
1300 if disk.get('vim_id'):
1301 block_device_mapping['_vd' + chr(base_disk_index)] = disk['vim_id']
1302 else:
1303 if 'image_id' in disk:
1304 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1305 chr(base_disk_index), imageRef=disk['image_id'])
1306 else:
1307 volume = self.cinder.volumes.create(size=disk['size'], name=name + '_vd' +
1308 chr(base_disk_index))
1309 created_items["volume:" + str(volume.id)] = True
1310 block_device_mapping['_vd' + chr(base_disk_index)] = volume.id
1311 base_disk_index += 1
1312
1313 # Wait until created volumes are with status available
1314 elapsed_time = 0
1315 while elapsed_time < volume_timeout:
1316 for created_item in created_items:
1317 v, _, volume_id = created_item.partition(":")
1318 if v == 'volume':
1319 if self.cinder.volumes.get(volume_id).status != 'available':
1320 break
1321 else: # all ready: break from while
1322 break
1323 time.sleep(5)
1324 elapsed_time += 5
1325 # If we exceeded the timeout rollback
1326 if elapsed_time >= volume_timeout:
1327 raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
1328 http_code=vimconn.HTTP_Request_Timeout)
1329 # get availability Zone
1330 vm_av_zone = self._get_vm_availability_zone(availability_zone_index, availability_zone_list)
1331
1332 self.logger.debug("nova.servers.create({}, {}, {}, nics={}, security_groups={}, "
1333 "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
1334 "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
1335 self.config.get("security_groups"), vm_av_zone,
1336 self.config.get('keypair'), userdata, config_drive,
1337 block_device_mapping))
1338 server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim,
1339 security_groups=self.config.get("security_groups"),
1340 # TODO remove security_groups in future versions. Already at neutron port
1341 availability_zone=vm_av_zone,
1342 key_name=self.config.get('keypair'),
1343 userdata=userdata,
1344 config_drive=config_drive,
1345 block_device_mapping=block_device_mapping
1346 ) # , description=description)
1347
1348 vm_start_time = time.time()
1349 # Previously mentioned workaround to wait until the VM is active and then disable the port-security
1350 if no_secured_ports:
1351 self.__wait_for_vm(server.id, 'ACTIVE')
1352
1353 for port_id in no_secured_ports:
1354 try:
1355 self.neutron.update_port(port_id,
1356 {"port": {"port_security_enabled": False, "security_groups": None}})
1357 except Exception as e:
1358 raise vimconn.vimconnException("It was not possible to disable port security for port {}".format(
1359 port_id))
1360 # print "DONE :-)", server
1361
1362 # pool_id = None
1363 if external_network:
1364 floating_ips = self.neutron.list_floatingips().get("floatingips", ())
1365 for floating_network in external_network:
1366 try:
1367 assigned = False
1368 while not assigned:
1369 if floating_ips:
1370 ip = floating_ips.pop(0)
1371 if ip.get("port_id", False) or ip.get('tenant_id') != server.tenant_id:
1372 continue
1373 if isinstance(floating_network['floating_ip'], str):
1374 if ip.get("floating_network_id") != floating_network['floating_ip']:
1375 continue
1376 free_floating_ip = ip["id"]
1377 else:
1378 if isinstance(floating_network['floating_ip'], str) and \
1379 floating_network['floating_ip'].lower() != "true":
1380 pool_id = floating_network['floating_ip']
1381 else:
1382 # Find the external network
1383 external_nets = list()
1384 for net in self.neutron.list_networks()['networks']:
1385 if net['router:external']:
1386 external_nets.append(net)
1387
1388 if len(external_nets) == 0:
1389 raise vimconn.vimconnException("Cannot create floating_ip automatically since no external "
1390 "network is present",
1391 http_code=vimconn.HTTP_Conflict)
1392 if len(external_nets) > 1:
1393 raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple "
1394 "external networks are present",
1395 http_code=vimconn.HTTP_Conflict)
1396
1397 pool_id = external_nets[0].get('id')
1398 param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}}
1399 try:
1400 # self.logger.debug("Creating floating IP")
1401 new_floating_ip = self.neutron.create_floatingip(param)
1402 free_floating_ip = new_floating_ip['floatingip']['id']
1403 except Exception as e:
1404 raise vimconn.vimconnException(type(e).__name__ + ": Cannot create new floating_ip " +
1405 str(e), http_code=vimconn.HTTP_Conflict)
1406
1407 while not assigned:
1408 try:
1409 # the vim_id key contains the neutron.port_id
1410 self.neutron.update_floatingip(free_floating_ip,
1411 {"floatingip": {"port_id": floating_network["vim_id"]}})
1412 # Using nove is deprecated on nova client 10.0
1413 assigned = True
1414 except Exception as e:
1415 # openstack need some time after VM creation to asign an IP. So retry if fails
1416 vm_status = self.nova.servers.get(server.id).status
1417 if vm_status != 'ACTIVE' and vm_status != 'ERROR':
1418 if time.time() - vm_start_time < server_timeout:
1419 time.sleep(5)
1420 continue
1421 raise vimconn.vimconnException(
1422 "Cannot create floating_ip: {} {}".format(type(e).__name__, e),
1423 http_code=vimconn.HTTP_Conflict)
1424
1425 except Exception as e:
1426 if not floating_network['exit_on_floating_ip_error']:
1427 self.logger.warning("Cannot create floating_ip. %s", str(e))
1428 continue
1429 raise
1430
1431 return server.id, created_items
1432 # except nvExceptions.NotFound as e:
1433 # error_value=-vimconn.HTTP_Not_Found
1434 # error_text= "vm instance %s not found" % vm_id
1435 # except TypeError as e:
1436 # raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request)
1437
1438 except Exception as e:
1439 server_id = None
1440 if server:
1441 server_id = server.id
1442 try:
1443 self.delete_vminstance(server_id, created_items)
1444 except Exception as e2:
1445 self.logger.error("new_vminstance rollback fail {}".format(e2))
1446
1447 self._format_exception(e)
1448
1449 def get_vminstance(self,vm_id):
1450 '''Returns the VM instance information from VIM'''
1451 #self.logger.debug("Getting VM from VIM")
1452 try:
1453 self._reload_connection()
1454 server = self.nova.servers.find(id=vm_id)
1455 #TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema)
1456 return server.to_dict()
1457 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
1458 self._format_exception(e)
1459
1460 def get_vminstance_console(self,vm_id, console_type="vnc"):
1461 '''
1462 Get a console for the virtual machine
1463 Params:
1464 vm_id: uuid of the VM
1465 console_type, can be:
1466 "novnc" (by default), "xvpvnc" for VNC types,
1467 "rdp-html5" for RDP types, "spice-html5" for SPICE types
1468 Returns dict with the console parameters:
1469 protocol: ssh, ftp, http, https, ...
1470 server: usually ip address
1471 port: the http, ssh, ... port
1472 suffix: extra text, e.g. the http path and query string
1473 '''
1474 self.logger.debug("Getting VM CONSOLE from VIM")
1475 try:
1476 self._reload_connection()
1477 server = self.nova.servers.find(id=vm_id)
1478 if console_type == None or console_type == "novnc":
1479 console_dict = server.get_vnc_console("novnc")
1480 elif console_type == "xvpvnc":
1481 console_dict = server.get_vnc_console(console_type)
1482 elif console_type == "rdp-html5":
1483 console_dict = server.get_rdp_console(console_type)
1484 elif console_type == "spice-html5":
1485 console_dict = server.get_spice_console(console_type)
1486 else:
1487 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type), http_code=vimconn.HTTP_Bad_Request)
1488
1489 console_dict1 = console_dict.get("console")
1490 if console_dict1:
1491 console_url = console_dict1.get("url")
1492 if console_url:
1493 #parse console_url
1494 protocol_index = console_url.find("//")
1495 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1496 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1497 if protocol_index < 0 or port_index<0 or suffix_index<0:
1498 return -vimconn.HTTP_Internal_Server_Error, "Unexpected response from VIM"
1499 console_dict={"protocol": console_url[0:protocol_index],
1500 "server": console_url[protocol_index+2:port_index],
1501 "port": console_url[port_index:suffix_index],
1502 "suffix": console_url[suffix_index+1:]
1503 }
1504 protocol_index += 2
1505 return console_dict
1506 raise vimconn.vimconnUnexpectedResponse("Unexpected response from VIM")
1507
1508 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.BadRequest, ConnectionError) as e:
1509 self._format_exception(e)
1510
1511 def delete_vminstance(self, vm_id, created_items=None):
1512 '''Removes a VM instance from VIM. Returns the old identifier
1513 '''
1514 #print "osconnector: Getting VM from VIM"
1515 if created_items == None:
1516 created_items = {}
1517 try:
1518 self._reload_connection()
1519 # delete VM ports attached to this networks before the virtual machine
1520 for k, v in created_items.items():
1521 if not v: # skip already deleted
1522 continue
1523 try:
1524 k_item, _, k_id = k.partition(":")
1525 if k_item == "port":
1526 self.neutron.delete_port(k_id)
1527 except Exception as e:
1528 self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
1529
1530 # #commented because detaching the volumes makes the servers.delete not work properly ?!?
1531 # #dettach volumes attached
1532 # server = self.nova.servers.get(vm_id)
1533 # volumes_attached_dict = server._info['os-extended-volumes:volumes_attached'] #volume['id']
1534 # #for volume in volumes_attached_dict:
1535 # # self.cinder.volumes.detach(volume['id'])
1536
1537 if vm_id:
1538 self.nova.servers.delete(vm_id)
1539
1540 # delete volumes. Although having detached, they should have in active status before deleting
1541 # we ensure in this loop
1542 keep_waiting = True
1543 elapsed_time = 0
1544 while keep_waiting and elapsed_time < volume_timeout:
1545 keep_waiting = False
1546 for k, v in created_items.items():
1547 if not v: # skip already deleted
1548 continue
1549 try:
1550 k_item, _, k_id = k.partition(":")
1551 if k_item == "volume":
1552 if self.cinder.volumes.get(k_id).status != 'available':
1553 keep_waiting = True
1554 else:
1555 self.cinder.volumes.delete(k_id)
1556 except Exception as e:
1557 self.logger.error("Error deleting volume: {}: {}".format(type(e).__name__, e))
1558 if keep_waiting:
1559 time.sleep(1)
1560 elapsed_time += 1
1561 return None
1562 except (nvExceptions.NotFound, ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError) as e:
1563 self._format_exception(e)
1564
1565 def refresh_vms_status(self, vm_list):
1566 '''Get the status of the virtual machines and their interfaces/ports
1567 Params: the list of VM identifiers
1568 Returns a dictionary with:
1569 vm_id: #VIM id of this Virtual Machine
1570 status: #Mandatory. Text with one of:
1571 # DELETED (not found at vim)
1572 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1573 # OTHER (Vim reported other status not understood)
1574 # ERROR (VIM indicates an ERROR status)
1575 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1576 # CREATING (on building process), ERROR
1577 # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address
1578 #
1579 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1580 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1581 interfaces:
1582 - vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1583 mac_address: #Text format XX:XX:XX:XX:XX:XX
1584 vim_net_id: #network id where this interface is connected
1585 vim_interface_id: #interface/port VIM id
1586 ip_address: #null, or text with IPv4, IPv6 address
1587 compute_node: #identification of compute node where PF,VF interface is allocated
1588 pci: #PCI address of the NIC that hosts the PF,VF
1589 vlan: #physical VLAN used for VF
1590 '''
1591 vm_dict={}
1592 self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM")
1593 for vm_id in vm_list:
1594 vm={}
1595 try:
1596 vm_vim = self.get_vminstance(vm_id)
1597 if vm_vim['status'] in vmStatus2manoFormat:
1598 vm['status'] = vmStatus2manoFormat[ vm_vim['status'] ]
1599 else:
1600 vm['status'] = "OTHER"
1601 vm['error_msg'] = "VIM status reported " + vm_vim['status']
1602
1603 vm['vim_info'] = self.serialize(vm_vim)
1604
1605 vm["interfaces"] = []
1606 if vm_vim.get('fault'):
1607 vm['error_msg'] = str(vm_vim['fault'])
1608 #get interfaces
1609 try:
1610 self._reload_connection()
1611 port_dict = self.neutron.list_ports(device_id=vm_id)
1612 for port in port_dict["ports"]:
1613 interface={}
1614 interface['vim_info'] = self.serialize(port)
1615 interface["mac_address"] = port.get("mac_address")
1616 interface["vim_net_id"] = port["network_id"]
1617 interface["vim_interface_id"] = port["id"]
1618 # check if OS-EXT-SRV-ATTR:host is there,
1619 # in case of non-admin credentials, it will be missing
1620 if vm_vim.get('OS-EXT-SRV-ATTR:host'):
1621 interface["compute_node"] = vm_vim['OS-EXT-SRV-ATTR:host']
1622 interface["pci"] = None
1623
1624 # check if binding:profile is there,
1625 # in case of non-admin credentials, it will be missing
1626 if port.get('binding:profile'):
1627 if port['binding:profile'].get('pci_slot'):
1628 # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00
1629 # TODO: This is just a workaround valid for niantinc. Find a better way to do so
1630 # CHANGE DDDD:BB:SS.F to DDDD:BB:00.(F%2) assuming there are 2 ports per nic
1631 pci = port['binding:profile']['pci_slot']
1632 # interface["pci"] = pci[:-4] + "00." + str(int(pci[-1]) % 2)
1633 interface["pci"] = pci
1634 interface["vlan"] = None
1635 #if network is of type vlan and port is of type direct (sr-iov) then set vlan id
1636 network = self.neutron.show_network(port["network_id"])
1637 if network['network'].get('provider:network_type') == 'vlan' and \
1638 port.get("binding:vnic_type") == "direct":
1639 interface["vlan"] = network['network'].get('provider:segmentation_id')
1640 ips=[]
1641 #look for floating ip address
1642 try:
1643 floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"])
1644 if floating_ip_dict.get("floatingips"):
1645 ips.append(floating_ip_dict["floatingips"][0].get("floating_ip_address") )
1646 except Exception:
1647 pass
1648
1649 for subnet in port["fixed_ips"]:
1650 ips.append(subnet["ip_address"])
1651 interface["ip_address"] = ";".join(ips)
1652 vm["interfaces"].append(interface)
1653 except Exception as e:
1654 self.logger.error("Error getting vm interface information {}: {}".format(type(e).__name__, e),
1655 exc_info=True)
1656 except vimconn.vimconnNotFoundException as e:
1657 self.logger.error("Exception getting vm status: %s", str(e))
1658 vm['status'] = "DELETED"
1659 vm['error_msg'] = str(e)
1660 except vimconn.vimconnException as e:
1661 self.logger.error("Exception getting vm status: %s", str(e))
1662 vm['status'] = "VIM_ERROR"
1663 vm['error_msg'] = str(e)
1664 vm_dict[vm_id] = vm
1665 return vm_dict
1666
1667 def action_vminstance(self, vm_id, action_dict, created_items={}):
1668 '''Send and action over a VM instance from VIM
1669 Returns None or the console dict if the action was successfully sent to the VIM'''
1670 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1671 try:
1672 self._reload_connection()
1673 server = self.nova.servers.find(id=vm_id)
1674 if "start" in action_dict:
1675 if action_dict["start"]=="rebuild":
1676 server.rebuild()
1677 else:
1678 if server.status=="PAUSED":
1679 server.unpause()
1680 elif server.status=="SUSPENDED":
1681 server.resume()
1682 elif server.status=="SHUTOFF":
1683 server.start()
1684 elif "pause" in action_dict:
1685 server.pause()
1686 elif "resume" in action_dict:
1687 server.resume()
1688 elif "shutoff" in action_dict or "shutdown" in action_dict:
1689 server.stop()
1690 elif "forceOff" in action_dict:
1691 server.stop() #TODO
1692 elif "terminate" in action_dict:
1693 server.delete()
1694 elif "createImage" in action_dict:
1695 server.create_image()
1696 #"path":path_schema,
1697 #"description":description_schema,
1698 #"name":name_schema,
1699 #"metadata":metadata_schema,
1700 #"imageRef": id_schema,
1701 #"disk": {"oneOf":[{"type": "null"}, {"type":"string"}] },
1702 elif "rebuild" in action_dict:
1703 server.rebuild(server.image['id'])
1704 elif "reboot" in action_dict:
1705 server.reboot() #reboot_type='SOFT'
1706 elif "console" in action_dict:
1707 console_type = action_dict["console"]
1708 if console_type == None or console_type == "novnc":
1709 console_dict = server.get_vnc_console("novnc")
1710 elif console_type == "xvpvnc":
1711 console_dict = server.get_vnc_console(console_type)
1712 elif console_type == "rdp-html5":
1713 console_dict = server.get_rdp_console(console_type)
1714 elif console_type == "spice-html5":
1715 console_dict = server.get_spice_console(console_type)
1716 else:
1717 raise vimconn.vimconnException("console type '{}' not allowed".format(console_type),
1718 http_code=vimconn.HTTP_Bad_Request)
1719 try:
1720 console_url = console_dict["console"]["url"]
1721 #parse console_url
1722 protocol_index = console_url.find("//")
1723 suffix_index = console_url[protocol_index+2:].find("/") + protocol_index+2
1724 port_index = console_url[protocol_index+2:suffix_index].find(":") + protocol_index+2
1725 if protocol_index < 0 or port_index<0 or suffix_index<0:
1726 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
1727 console_dict2={"protocol": console_url[0:protocol_index],
1728 "server": console_url[protocol_index+2 : port_index],
1729 "port": int(console_url[port_index+1 : suffix_index]),
1730 "suffix": console_url[suffix_index+1:]
1731 }
1732 return console_dict2
1733 except Exception as e:
1734 raise vimconn.vimconnException("Unexpected response from VIM " + str(console_dict))
1735
1736 return None
1737 except (ksExceptions.ClientException, nvExceptions.ClientException, nvExceptions.NotFound, ConnectionError) as e:
1738 self._format_exception(e)
1739 #TODO insert exception vimconn.HTTP_Unauthorized
1740
1741 ####### VIO Specific Changes #########
1742 def _generate_vlanID(self):
1743 """
1744 Method to get unused vlanID
1745 Args:
1746 None
1747 Returns:
1748 vlanID
1749 """
1750 #Get used VLAN IDs
1751 usedVlanIDs = []
1752 networks = self.get_network_list()
1753 for net in networks:
1754 if net.get('provider:segmentation_id'):
1755 usedVlanIDs.append(net.get('provider:segmentation_id'))
1756 used_vlanIDs = set(usedVlanIDs)
1757
1758 #find unused VLAN ID
1759 for vlanID_range in self.config.get('dataplane_net_vlan_range'):
1760 try:
1761 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
1762 for vlanID in range(start_vlanid, end_vlanid + 1):
1763 if vlanID not in used_vlanIDs:
1764 return vlanID
1765 except Exception as exp:
1766 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1767 else:
1768 raise vimconn.vimconnConflictException("Unable to create the SRIOV VLAN network."\
1769 " All given Vlan IDs {} are in use.".format(self.config.get('dataplane_net_vlan_range')))
1770
1771
1772 def _generate_multisegment_vlanID(self):
1773 """
1774 Method to get unused vlanID
1775 Args:
1776 None
1777 Returns:
1778 vlanID
1779 """
1780 #Get used VLAN IDs
1781 usedVlanIDs = []
1782 networks = self.get_network_list()
1783 for net in networks:
1784 if net.get('provider:network_type') == "vlan" and net.get('provider:segmentation_id'):
1785 usedVlanIDs.append(net.get('provider:segmentation_id'))
1786 elif net.get('segments'):
1787 for segment in net.get('segments'):
1788 if segment.get('provider:network_type') == "vlan" and segment.get('provider:segmentation_id'):
1789 usedVlanIDs.append(segment.get('provider:segmentation_id'))
1790 used_vlanIDs = set(usedVlanIDs)
1791
1792 #find unused VLAN ID
1793 for vlanID_range in self.config.get('multisegment_vlan_range'):
1794 try:
1795 start_vlanid , end_vlanid = map(int, vlanID_range.replace(" ", "").split("-"))
1796 for vlanID in range(start_vlanid, end_vlanid + 1):
1797 if vlanID not in used_vlanIDs:
1798 return vlanID
1799 except Exception as exp:
1800 raise vimconn.vimconnException("Exception {} occurred while generating VLAN ID.".format(exp))
1801 else:
1802 raise vimconn.vimconnConflictException("Unable to create the VLAN segment."\
1803 " All VLAN IDs {} are in use.".format(self.config.get('multisegment_vlan_range')))
1804
1805
1806 def _validate_vlan_ranges(self, input_vlan_range, text_vlan_range):
1807 """
1808 Method to validate user given vlanID ranges
1809 Args: None
1810 Returns: None
1811 """
1812 for vlanID_range in input_vlan_range:
1813 vlan_range = vlanID_range.replace(" ", "")
1814 #validate format
1815 vlanID_pattern = r'(\d)*-(\d)*$'
1816 match_obj = re.match(vlanID_pattern, vlan_range)
1817 if not match_obj:
1818 raise vimconn.vimconnConflictException("Invalid VLAN range for {}: {}.You must provide "\
1819 "'{}' in format [start_ID - end_ID].".format(text_vlan_range, vlanID_range, text_vlan_range))
1820
1821 start_vlanid , end_vlanid = map(int,vlan_range.split("-"))
1822 if start_vlanid <= 0 :
1823 raise vimconn.vimconnConflictException("Invalid VLAN range for {}: {}."\
1824 "Start ID can not be zero. For VLAN "\
1825 "networks valid IDs are 1 to 4094 ".format(text_vlan_range, vlanID_range))
1826 if end_vlanid > 4094 :
1827 raise vimconn.vimconnConflictException("Invalid VLAN range for {}: {}."\
1828 "End VLAN ID can not be greater than 4094. For VLAN "\
1829 "networks valid IDs are 1 to 4094 ".format(text_vlan_range, vlanID_range))
1830
1831 if start_vlanid > end_vlanid:
1832 raise vimconn.vimconnConflictException("Invalid VLAN range for {}: {}."\
1833 "You must provide '{}' in format start_ID - end_ID and "\
1834 "start_ID < end_ID ".format(text_vlan_range, vlanID_range, text_vlan_range))
1835
1836 #NOT USED FUNCTIONS
1837
1838 def new_external_port(self, port_data):
1839 #TODO openstack if needed
1840 '''Adds a external port to VIM'''
1841 '''Returns the port identifier'''
1842 return -vimconn.HTTP_Internal_Server_Error, "osconnector.new_external_port() not implemented"
1843
1844 def connect_port_network(self, port_id, network_id, admin=False):
1845 #TODO openstack if needed
1846 '''Connects a external port to a network'''
1847 '''Returns status code of the VIM response'''
1848 return -vimconn.HTTP_Internal_Server_Error, "osconnector.connect_port_network() not implemented"
1849
1850 def new_user(self, user_name, user_passwd, tenant_id=None):
1851 '''Adds a new user to openstack VIM'''
1852 '''Returns the user identifier'''
1853 self.logger.debug("osconnector: Adding a new user to VIM")
1854 try:
1855 self._reload_connection()
1856 user=self.keystone.users.create(user_name, password=user_passwd, default_project=tenant_id)
1857 #self.keystone.tenants.add_user(self.k_creds["username"], #role)
1858 return user.id
1859 except ksExceptions.ConnectionError as e:
1860 error_value=-vimconn.HTTP_Bad_Request
1861 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1862 except ksExceptions.ClientException as e: #TODO remove
1863 error_value=-vimconn.HTTP_Bad_Request
1864 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1865 #TODO insert exception vimconn.HTTP_Unauthorized
1866 #if reaching here is because an exception
1867 self.logger.debug("new_user " + error_text)
1868 return error_value, error_text
1869
1870 def delete_user(self, user_id):
1871 '''Delete a user from openstack VIM'''
1872 '''Returns the user identifier'''
1873 if self.debug:
1874 print("osconnector: Deleting a user from VIM")
1875 try:
1876 self._reload_connection()
1877 self.keystone.users.delete(user_id)
1878 return 1, user_id
1879 except ksExceptions.ConnectionError as e:
1880 error_value=-vimconn.HTTP_Bad_Request
1881 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1882 except ksExceptions.NotFound as e:
1883 error_value=-vimconn.HTTP_Not_Found
1884 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1885 except ksExceptions.ClientException as e: #TODO remove
1886 error_value=-vimconn.HTTP_Bad_Request
1887 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1888 #TODO insert exception vimconn.HTTP_Unauthorized
1889 #if reaching here is because an exception
1890 self.logger.debug("delete_tenant " + error_text)
1891 return error_value, error_text
1892
1893 def get_hosts_info(self):
1894 '''Get the information of deployed hosts
1895 Returns the hosts content'''
1896 if self.debug:
1897 print("osconnector: Getting Host info from VIM")
1898 try:
1899 h_list=[]
1900 self._reload_connection()
1901 hypervisors = self.nova.hypervisors.list()
1902 for hype in hypervisors:
1903 h_list.append( hype.to_dict() )
1904 return 1, {"hosts":h_list}
1905 except nvExceptions.NotFound as e:
1906 error_value=-vimconn.HTTP_Not_Found
1907 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1908 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1909 error_value=-vimconn.HTTP_Bad_Request
1910 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1911 #TODO insert exception vimconn.HTTP_Unauthorized
1912 #if reaching here is because an exception
1913 self.logger.debug("get_hosts_info " + error_text)
1914 return error_value, error_text
1915
1916 def get_hosts(self, vim_tenant):
1917 '''Get the hosts and deployed instances
1918 Returns the hosts content'''
1919 r, hype_dict = self.get_hosts_info()
1920 if r<0:
1921 return r, hype_dict
1922 hypervisors = hype_dict["hosts"]
1923 try:
1924 servers = self.nova.servers.list()
1925 for hype in hypervisors:
1926 for server in servers:
1927 if server.to_dict()['OS-EXT-SRV-ATTR:hypervisor_hostname']==hype['hypervisor_hostname']:
1928 if 'vm' in hype:
1929 hype['vm'].append(server.id)
1930 else:
1931 hype['vm'] = [server.id]
1932 return 1, hype_dict
1933 except nvExceptions.NotFound as e:
1934 error_value=-vimconn.HTTP_Not_Found
1935 error_text= (str(e) if len(e.args)==0 else str(e.args[0]))
1936 except (ksExceptions.ClientException, nvExceptions.ClientException) as e:
1937 error_value=-vimconn.HTTP_Bad_Request
1938 error_text= type(e).__name__ + ": "+ (str(e) if len(e.args)==0 else str(e.args[0]))
1939 #TODO insert exception vimconn.HTTP_Unauthorized
1940 #if reaching here is because an exception
1941 self.logger.debug("get_hosts " + error_text)
1942 return error_value, error_text
1943
1944 def new_classification(self, name, ctype, definition):
1945 self.logger.debug('Adding a new (Traffic) Classification to VIM, named %s', name)
1946 try:
1947 new_class = None
1948 self._reload_connection()
1949 if ctype not in supportedClassificationTypes:
1950 raise vimconn.vimconnNotSupportedException(
1951 'OpenStack VIM connector doesn\'t support provided '
1952 'Classification Type {}, supported ones are: '
1953 '{}'.format(ctype, supportedClassificationTypes))
1954 if not self._validate_classification(ctype, definition):
1955 raise vimconn.vimconnException(
1956 'Incorrect Classification definition '
1957 'for the type specified.')
1958 classification_dict = definition
1959 classification_dict['name'] = name
1960
1961 new_class = self.neutron.create_sfc_flow_classifier(
1962 {'flow_classifier': classification_dict})
1963 return new_class['flow_classifier']['id']
1964 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1965 neExceptions.NeutronException, ConnectionError) as e:
1966 self.logger.error(
1967 'Creation of Classification failed.')
1968 self._format_exception(e)
1969
1970 def get_classification(self, class_id):
1971 self.logger.debug(" Getting Classification %s from VIM", class_id)
1972 filter_dict = {"id": class_id}
1973 class_list = self.get_classification_list(filter_dict)
1974 if len(class_list) == 0:
1975 raise vimconn.vimconnNotFoundException(
1976 "Classification '{}' not found".format(class_id))
1977 elif len(class_list) > 1:
1978 raise vimconn.vimconnConflictException(
1979 "Found more than one Classification with this criteria")
1980 classification = class_list[0]
1981 return classification
1982
1983 def get_classification_list(self, filter_dict={}):
1984 self.logger.debug("Getting Classifications from VIM filter: '%s'",
1985 str(filter_dict))
1986 try:
1987 filter_dict_os = filter_dict.copy()
1988 self._reload_connection()
1989 if self.api_version3 and "tenant_id" in filter_dict_os:
1990 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
1991 classification_dict = self.neutron.list_sfc_flow_classifiers(
1992 **filter_dict_os)
1993 classification_list = classification_dict["flow_classifiers"]
1994 self.__classification_os2mano(classification_list)
1995 return classification_list
1996 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
1997 neExceptions.NeutronException, ConnectionError) as e:
1998 self._format_exception(e)
1999
2000 def delete_classification(self, class_id):
2001 self.logger.debug("Deleting Classification '%s' from VIM", class_id)
2002 try:
2003 self._reload_connection()
2004 self.neutron.delete_sfc_flow_classifier(class_id)
2005 return class_id
2006 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2007 ksExceptions.ClientException, neExceptions.NeutronException,
2008 ConnectionError) as e:
2009 self._format_exception(e)
2010
2011 def new_sfi(self, name, ingress_ports, egress_ports, sfc_encap=True):
2012 self.logger.debug("Adding a new Service Function Instance to VIM, named '%s'", name)
2013 try:
2014 new_sfi = None
2015 self._reload_connection()
2016 correlation = None
2017 if sfc_encap:
2018 correlation = 'nsh'
2019 if len(ingress_ports) != 1:
2020 raise vimconn.vimconnNotSupportedException(
2021 "OpenStack VIM connector can only have "
2022 "1 ingress port per SFI")
2023 if len(egress_ports) != 1:
2024 raise vimconn.vimconnNotSupportedException(
2025 "OpenStack VIM connector can only have "
2026 "1 egress port per SFI")
2027 sfi_dict = {'name': name,
2028 'ingress': ingress_ports[0],
2029 'egress': egress_ports[0],
2030 'service_function_parameters': {
2031 'correlation': correlation}}
2032 new_sfi = self.neutron.create_sfc_port_pair({'port_pair': sfi_dict})
2033 return new_sfi['port_pair']['id']
2034 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2035 neExceptions.NeutronException, ConnectionError) as e:
2036 if new_sfi:
2037 try:
2038 self.neutron.delete_sfc_port_pair(
2039 new_sfi['port_pair']['id'])
2040 except Exception:
2041 self.logger.error(
2042 'Creation of Service Function Instance failed, with '
2043 'subsequent deletion failure as well.')
2044 self._format_exception(e)
2045
2046 def get_sfi(self, sfi_id):
2047 self.logger.debug('Getting Service Function Instance %s from VIM', sfi_id)
2048 filter_dict = {"id": sfi_id}
2049 sfi_list = self.get_sfi_list(filter_dict)
2050 if len(sfi_list) == 0:
2051 raise vimconn.vimconnNotFoundException("Service Function Instance '{}' not found".format(sfi_id))
2052 elif len(sfi_list) > 1:
2053 raise vimconn.vimconnConflictException(
2054 'Found more than one Service Function Instance '
2055 'with this criteria')
2056 sfi = sfi_list[0]
2057 return sfi
2058
2059 def get_sfi_list(self, filter_dict={}):
2060 self.logger.debug("Getting Service Function Instances from VIM filter: '%s'", str(filter_dict))
2061 try:
2062 self._reload_connection()
2063 filter_dict_os = filter_dict.copy()
2064 if self.api_version3 and "tenant_id" in filter_dict_os:
2065 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
2066 sfi_dict = self.neutron.list_sfc_port_pairs(**filter_dict_os)
2067 sfi_list = sfi_dict["port_pairs"]
2068 self.__sfi_os2mano(sfi_list)
2069 return sfi_list
2070 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2071 neExceptions.NeutronException, ConnectionError) as e:
2072 self._format_exception(e)
2073
2074 def delete_sfi(self, sfi_id):
2075 self.logger.debug("Deleting Service Function Instance '%s' "
2076 "from VIM", sfi_id)
2077 try:
2078 self._reload_connection()
2079 self.neutron.delete_sfc_port_pair(sfi_id)
2080 return sfi_id
2081 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2082 ksExceptions.ClientException, neExceptions.NeutronException,
2083 ConnectionError) as e:
2084 self._format_exception(e)
2085
2086 def new_sf(self, name, sfis, sfc_encap=True):
2087 self.logger.debug("Adding a new Service Function to VIM, named '%s'", name)
2088 try:
2089 new_sf = None
2090 self._reload_connection()
2091 # correlation = None
2092 # if sfc_encap:
2093 # correlation = 'nsh'
2094 for instance in sfis:
2095 sfi = self.get_sfi(instance)
2096 if sfi.get('sfc_encap') != sfc_encap:
2097 raise vimconn.vimconnNotSupportedException(
2098 "OpenStack VIM connector requires all SFIs of the "
2099 "same SF to share the same SFC Encapsulation")
2100 sf_dict = {'name': name,
2101 'port_pairs': sfis}
2102 new_sf = self.neutron.create_sfc_port_pair_group({
2103 'port_pair_group': sf_dict})
2104 return new_sf['port_pair_group']['id']
2105 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2106 neExceptions.NeutronException, ConnectionError) as e:
2107 if new_sf:
2108 try:
2109 self.neutron.delete_sfc_port_pair_group(
2110 new_sf['port_pair_group']['id'])
2111 except Exception:
2112 self.logger.error(
2113 'Creation of Service Function failed, with '
2114 'subsequent deletion failure as well.')
2115 self._format_exception(e)
2116
2117 def get_sf(self, sf_id):
2118 self.logger.debug("Getting Service Function %s from VIM", sf_id)
2119 filter_dict = {"id": sf_id}
2120 sf_list = self.get_sf_list(filter_dict)
2121 if len(sf_list) == 0:
2122 raise vimconn.vimconnNotFoundException(
2123 "Service Function '{}' not found".format(sf_id))
2124 elif len(sf_list) > 1:
2125 raise vimconn.vimconnConflictException(
2126 "Found more than one Service Function with this criteria")
2127 sf = sf_list[0]
2128 return sf
2129
2130 def get_sf_list(self, filter_dict={}):
2131 self.logger.debug("Getting Service Function from VIM filter: '%s'",
2132 str(filter_dict))
2133 try:
2134 self._reload_connection()
2135 filter_dict_os = filter_dict.copy()
2136 if self.api_version3 and "tenant_id" in filter_dict_os:
2137 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
2138 sf_dict = self.neutron.list_sfc_port_pair_groups(**filter_dict_os)
2139 sf_list = sf_dict["port_pair_groups"]
2140 self.__sf_os2mano(sf_list)
2141 return sf_list
2142 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2143 neExceptions.NeutronException, ConnectionError) as e:
2144 self._format_exception(e)
2145
2146 def delete_sf(self, sf_id):
2147 self.logger.debug("Deleting Service Function '%s' from VIM", sf_id)
2148 try:
2149 self._reload_connection()
2150 self.neutron.delete_sfc_port_pair_group(sf_id)
2151 return sf_id
2152 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2153 ksExceptions.ClientException, neExceptions.NeutronException,
2154 ConnectionError) as e:
2155 self._format_exception(e)
2156
2157 def new_sfp(self, name, classifications, sfs, sfc_encap=True, spi=None):
2158 self.logger.debug("Adding a new Service Function Path to VIM, named '%s'", name)
2159 try:
2160 new_sfp = None
2161 self._reload_connection()
2162 # In networking-sfc the MPLS encapsulation is legacy
2163 # should be used when no full SFC Encapsulation is intended
2164 correlation = 'mpls'
2165 if sfc_encap:
2166 correlation = 'nsh'
2167 sfp_dict = {'name': name,
2168 'flow_classifiers': classifications,
2169 'port_pair_groups': sfs,
2170 'chain_parameters': {'correlation': correlation}}
2171 if spi:
2172 sfp_dict['chain_id'] = spi
2173 new_sfp = self.neutron.create_sfc_port_chain({'port_chain': sfp_dict})
2174 return new_sfp["port_chain"]["id"]
2175 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2176 neExceptions.NeutronException, ConnectionError) as e:
2177 if new_sfp:
2178 try:
2179 self.neutron.delete_sfc_port_chain(new_sfp['port_chain']['id'])
2180 except Exception:
2181 self.logger.error(
2182 'Creation of Service Function Path failed, with '
2183 'subsequent deletion failure as well.')
2184 self._format_exception(e)
2185
2186 def get_sfp(self, sfp_id):
2187 self.logger.debug(" Getting Service Function Path %s from VIM", sfp_id)
2188 filter_dict = {"id": sfp_id}
2189 sfp_list = self.get_sfp_list(filter_dict)
2190 if len(sfp_list) == 0:
2191 raise vimconn.vimconnNotFoundException(
2192 "Service Function Path '{}' not found".format(sfp_id))
2193 elif len(sfp_list) > 1:
2194 raise vimconn.vimconnConflictException(
2195 "Found more than one Service Function Path with this criteria")
2196 sfp = sfp_list[0]
2197 return sfp
2198
2199 def get_sfp_list(self, filter_dict={}):
2200 self.logger.debug("Getting Service Function Paths from VIM filter: '%s'", str(filter_dict))
2201 try:
2202 self._reload_connection()
2203 filter_dict_os = filter_dict.copy()
2204 if self.api_version3 and "tenant_id" in filter_dict_os:
2205 filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id')
2206 sfp_dict = self.neutron.list_sfc_port_chains(**filter_dict_os)
2207 sfp_list = sfp_dict["port_chains"]
2208 self.__sfp_os2mano(sfp_list)
2209 return sfp_list
2210 except (neExceptions.ConnectionFailed, ksExceptions.ClientException,
2211 neExceptions.NeutronException, ConnectionError) as e:
2212 self._format_exception(e)
2213
2214 def delete_sfp(self, sfp_id):
2215 self.logger.debug("Deleting Service Function Path '%s' from VIM", sfp_id)
2216 try:
2217 self._reload_connection()
2218 self.neutron.delete_sfc_port_chain(sfp_id)
2219 return sfp_id
2220 except (neExceptions.ConnectionFailed, neExceptions.NeutronException,
2221 ksExceptions.ClientException, neExceptions.NeutronException,
2222 ConnectionError) as e:
2223 self._format_exception(e)