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