X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FRO.git;a=blobdiff_plain;f=RO-VIM-openstack%2Fosm_rovim_openstack%2Fvimconn_openstack.py;h=0a39041205ce8b06e0836316138cb42ead17b97d;hp=7cf5e883881ec51476105f05e7af25a24ace906f;hb=36cad669a99ed1655dc39b1c37aea6197ddbab55;hpb=1d213f4c8825da8347ffb07b3eaa1315f0fab698 diff --git a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py index 7cf5e883..0a390412 100644 --- a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py +++ b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py @@ -30,52 +30,54 @@ to the VIM connector's SFC resources as follows: - Service Function Path (OSM) -> Port Chain (Neutron) """ -from osm_ro_plugin import vimconn -# import json +import copy +from http.client import HTTPException +import json import logging -import netaddr -import time -import yaml +from pprint import pformat import random import re -import copy -from pprint import pformat +import time -from novaclient import client as nClient, exceptions as nvExceptions -from keystoneauth1.identity import v2, v3 +from cinderclient import client as cClient +from glanceclient import client as glClient +import glanceclient.exc as gl1Exceptions from keystoneauth1 import session +from keystoneauth1.identity import v2, v3 import keystoneclient.exceptions as ksExceptions -import keystoneclient.v3.client as ksClient_v3 import keystoneclient.v2_0.client as ksClient_v2 -from glanceclient import client as glClient -import glanceclient.exc as gl1Exceptions -from cinderclient import client as cClient -from http.client import HTTPException # TODO py3 check that this base exception matches python2 httplib.HTTPException -from neutronclient.neutron import client as neClient +import keystoneclient.v3.client as ksClient_v3 +import netaddr from neutronclient.common import exceptions as neExceptions +from neutronclient.neutron import client as neClient +from novaclient import client as nClient, exceptions as nvExceptions +from osm_ro_plugin import vimconn from requests.exceptions import ConnectionError +import yaml __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes, xFlow Research, Igor D.C., Eduardo Sousa" __date__ = "$22-sep-2017 23:59:59$" """contain the openstack virtual machine status to openmano status""" -vmStatus2manoFormat = {'ACTIVE': 'ACTIVE', - 'PAUSED': 'PAUSED', - 'SUSPENDED': 'SUSPENDED', - 'SHUTOFF': 'INACTIVE', - 'BUILD': 'BUILD', - 'ERROR': 'ERROR', - 'DELETED': 'DELETED' - } -netStatus2manoFormat = {'ACTIVE': 'ACTIVE', - 'PAUSED': 'PAUSED', - 'INACTIVE': 'INACTIVE', - 'BUILD': 'BUILD', - 'ERROR': 'ERROR', - 'DELETED': 'DELETED' - } - -supportedClassificationTypes = ['legacy_flow_classifier'] +vmStatus2manoFormat = { + "ACTIVE": "ACTIVE", + "PAUSED": "PAUSED", + "SUSPENDED": "SUSPENDED", + "SHUTOFF": "INACTIVE", + "BUILD": "BUILD", + "ERROR": "ERROR", + "DELETED": "DELETED", +} +netStatus2manoFormat = { + "ACTIVE": "ACTIVE", + "PAUSED": "PAUSED", + "INACTIVE": "INACTIVE", + "BUILD": "BUILD", + "ERROR": "ERROR", + "DELETED": "DELETED", +} + +supportedClassificationTypes = ["legacy_flow_classifier"] # global var to have a timeout creating and deleting volumes volume_timeout = 1800 @@ -94,74 +96,117 @@ class SafeDumper(yaml.SafeDumper): class vimconnector(vimconn.VimConnector): - def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, - log_level=None, config={}, persistent_info={}): + def __init__( + self, + uuid, + name, + tenant_id, + tenant_name, + url, + url_admin=None, + user=None, + passwd=None, + log_level=None, + config={}, + persistent_info={}, + ): """using common constructor parameters. In this case 'url' is the keystone authorization url, 'url_admin' is not use """ - api_version = config.get('APIversion') - if api_version and api_version not in ('v3.3', 'v2.0', '2', '3'): - raise vimconn.VimConnException("Invalid value '{}' for config:APIversion. " - "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version)) - vim_type = config.get('vim_type') - if vim_type and vim_type not in ('vio', 'VIO'): - raise vimconn.VimConnException("Invalid value '{}' for config:vim_type." - "Allowed values are 'vio' or 'VIO'".format(vim_type)) - - if config.get('dataplane_net_vlan_range') is not None: - # validate vlan ranges provided by user - self._validate_vlan_ranges(config.get('dataplane_net_vlan_range'), 'dataplane_net_vlan_range') + api_version = config.get("APIversion") - if config.get('multisegment_vlan_range') is not None: + if api_version and api_version not in ("v3.3", "v2.0", "2", "3"): + raise vimconn.VimConnException( + "Invalid value '{}' for config:APIversion. " + "Allowed values are 'v3.3', 'v2.0', '2' or '3'".format(api_version) + ) + + vim_type = config.get("vim_type") + + if vim_type and vim_type not in ("vio", "VIO"): + raise vimconn.VimConnException( + "Invalid value '{}' for config:vim_type." + "Allowed values are 'vio' or 'VIO'".format(vim_type) + ) + + if config.get("dataplane_net_vlan_range") is not None: # validate vlan ranges provided by user - self._validate_vlan_ranges(config.get('multisegment_vlan_range'), 'multisegment_vlan_range') + self._validate_vlan_ranges( + config.get("dataplane_net_vlan_range"), "dataplane_net_vlan_range" + ) - vimconn.VimConnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, - config) + if config.get("multisegment_vlan_range") is not None: + # validate vlan ranges provided by user + self._validate_vlan_ranges( + config.get("multisegment_vlan_range"), "multisegment_vlan_range" + ) + + vimconn.VimConnector.__init__( + self, + uuid, + name, + tenant_id, + tenant_name, + url, + url_admin, + user, + passwd, + log_level, + config, + ) if self.config.get("insecure") and self.config.get("ca_cert"): - raise vimconn.VimConnException("options insecure and ca_cert are mutually exclusive") + raise vimconn.VimConnException( + "options insecure and ca_cert are mutually exclusive" + ) + self.verify = True + if self.config.get("insecure"): self.verify = False + if self.config.get("ca_cert"): self.verify = self.config.get("ca_cert") if not url: - raise TypeError('url param can not be NoneType') + raise TypeError("url param can not be NoneType") + self.persistent_info = persistent_info - self.availability_zone = persistent_info.get('availability_zone', None) - self.session = persistent_info.get('session', {'reload_client': True}) - self.my_tenant_id = self.session.get('my_tenant_id') - self.nova = self.session.get('nova') - self.neutron = self.session.get('neutron') - self.cinder = self.session.get('cinder') - self.glance = self.session.get('glance') - # self.glancev1 = self.session.get('glancev1') - self.keystone = self.session.get('keystone') - self.api_version3 = self.session.get('api_version3') + self.availability_zone = persistent_info.get("availability_zone", None) + self.session = persistent_info.get("session", {"reload_client": True}) + self.my_tenant_id = self.session.get("my_tenant_id") + self.nova = self.session.get("nova") + self.neutron = self.session.get("neutron") + self.cinder = self.session.get("cinder") + self.glance = self.session.get("glance") + # self.glancev1 = self.session.get("glancev1") + self.keystone = self.session.get("keystone") + self.api_version3 = self.session.get("api_version3") self.vim_type = self.config.get("vim_type") + if self.vim_type: self.vim_type = self.vim_type.upper() + if self.config.get("use_internal_endpoint"): self.endpoint_type = "internalURL" else: self.endpoint_type = None - logging.getLogger('urllib3').setLevel(logging.WARNING) - logging.getLogger('keystoneauth').setLevel(logging.WARNING) - logging.getLogger('novaclient').setLevel(logging.WARNING) - self.logger = logging.getLogger('openmano.vim.openstack') + logging.getLogger("urllib3").setLevel(logging.WARNING) + logging.getLogger("keystoneauth").setLevel(logging.WARNING) + logging.getLogger("novaclient").setLevel(logging.WARNING) + self.logger = logging.getLogger("ro.vim.openstack") # allow security_groups to be a list or a single string - if isinstance(self.config.get('security_groups'), str): - self.config['security_groups'] = [self.config['security_groups']] + if isinstance(self.config.get("security_groups"), str): + self.config["security_groups"] = [self.config["security_groups"]] + self.security_groups_id = None # ###### VIO Specific Changes ######### if self.vim_type == "VIO": - self.logger = logging.getLogger('openmano.vim.vio') + self.logger = logging.getLogger("ro.vim.vio") if log_level: self.logger.setLevel(getattr(logging, log_level)) @@ -169,9 +214,9 @@ class vimconnector(vimconn.VimConnector): def __getitem__(self, index): """Get individuals parameters. Throw KeyError""" - if index == 'project_domain_id': + if index == "project_domain_id": return self.config.get("project_domain_id") - elif index == 'user_domain_id': + elif index == "user_domain_id": return self.config.get("user_domain_id") else: return vimconn.VimConnector.__getitem__(self, index) @@ -179,13 +224,14 @@ class vimconnector(vimconn.VimConnector): def __setitem__(self, index, value): """Set individuals parameters and it is marked as dirty so to force connection reload. Throw KeyError""" - if index == 'project_domain_id': + if index == "project_domain_id": self.config["project_domain_id"] = value - elif index == 'user_domain_id': + elif index == "user_domain_id": self.config["user_domain_id"] = value else: vimconn.VimConnector.__setitem__(self, index, value) - self.session['reload_client'] = True + + self.session["reload_client"] = True def serialize(self, value): """Serialization of python basic types. @@ -198,11 +244,16 @@ class vimconnector(vimconn.VimConnector): return value try: - return yaml.dump(value, Dumper=SafeDumper, - default_flow_style=True, width=256) + return yaml.dump( + value, Dumper=SafeDumper, default_flow_style=True, width=256 + ) except yaml.representer.RepresenterError: - self.logger.debug('The following entity cannot be serialized in YAML:\n\n%s\n\n', pformat(value), - exc_info=True) + self.logger.debug( + "The following entity cannot be serialized in YAML:\n\n%s\n\n", + pformat(value), + exc_info=True, + ) + return str(value) def _reload_connection(self): @@ -210,83 +261,132 @@ class vimconnector(vimconn.VimConnector): Throw keystoneclient.apiclient.exceptions.AuthorizationFailure """ # TODO control the timing and possible token timeout, but it seams that python client does this task for us :-) - if self.session['reload_client']: - if self.config.get('APIversion'): - self.api_version3 = self.config['APIversion'] == 'v3.3' or self.config['APIversion'] == '3' + if self.session["reload_client"]: + if self.config.get("APIversion"): + self.api_version3 = ( + self.config["APIversion"] == "v3.3" + or self.config["APIversion"] == "3" + ) else: # get from ending auth_url that end with v3 or with v2.0 - self.api_version3 = self.url.endswith("/v3") or self.url.endswith("/v3/") - self.session['api_version3'] = self.api_version3 + self.api_version3 = self.url.endswith("/v3") or self.url.endswith( + "/v3/" + ) + + self.session["api_version3"] = self.api_version3 + if self.api_version3: - if self.config.get('project_domain_id') or self.config.get('project_domain_name'): + if self.config.get("project_domain_id") or self.config.get( + "project_domain_name" + ): project_domain_id_default = None else: - project_domain_id_default = 'default' - if self.config.get('user_domain_id') or self.config.get('user_domain_name'): + project_domain_id_default = "default" + + if self.config.get("user_domain_id") or self.config.get( + "user_domain_name" + ): user_domain_id_default = None else: - user_domain_id_default = 'default' - auth = v3.Password(auth_url=self.url, - username=self.user, - password=self.passwd, - project_name=self.tenant_name, - project_id=self.tenant_id, - project_domain_id=self.config.get('project_domain_id', project_domain_id_default), - user_domain_id=self.config.get('user_domain_id', user_domain_id_default), - project_domain_name=self.config.get('project_domain_name'), - user_domain_name=self.config.get('user_domain_name')) + user_domain_id_default = "default" + auth = v3.Password( + auth_url=self.url, + username=self.user, + password=self.passwd, + project_name=self.tenant_name, + project_id=self.tenant_id, + project_domain_id=self.config.get( + "project_domain_id", project_domain_id_default + ), + user_domain_id=self.config.get( + "user_domain_id", user_domain_id_default + ), + project_domain_name=self.config.get("project_domain_name"), + user_domain_name=self.config.get("user_domain_name"), + ) else: - auth = v2.Password(auth_url=self.url, - username=self.user, - password=self.passwd, - tenant_name=self.tenant_name, - tenant_id=self.tenant_id) + auth = v2.Password( + auth_url=self.url, + username=self.user, + password=self.passwd, + tenant_name=self.tenant_name, + tenant_id=self.tenant_id, + ) + sess = session.Session(auth=auth, verify=self.verify) # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River # Titanium cloud and StarlingX - region_name = self.config.get('region_name') + region_name = self.config.get("region_name") + if self.api_version3: - self.keystone = ksClient_v3.Client(session=sess, endpoint_type=self.endpoint_type, - region_name=region_name) + self.keystone = ksClient_v3.Client( + session=sess, + endpoint_type=self.endpoint_type, + region_name=region_name, + ) else: - self.keystone = ksClient_v2.Client(session=sess, endpoint_type=self.endpoint_type) - self.session['keystone'] = self.keystone - # In order to enable microversion functionality an explicit microversion must be specified in 'config'. + self.keystone = ksClient_v2.Client( + session=sess, endpoint_type=self.endpoint_type + ) + + self.session["keystone"] = self.keystone + # In order to enable microversion functionality an explicit microversion must be specified in "config". # This implementation approach is due to the warning message in # https://developer.openstack.org/api-guide/compute/microversions.html # where it is stated that microversion backwards compatibility is not guaranteed and clients should # always require an specific microversion. - # To be able to use 'device role tagging' functionality define 'microversion: 2.32' in datacenter config + # To be able to use "device role tagging" functionality define "microversion: 2.32" in datacenter config version = self.config.get("microversion") + if not version: version = "2.1" + # addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River # Titanium cloud and StarlingX - self.nova = self.session['nova'] = nClient.Client(str(version), session=sess, - endpoint_type=self.endpoint_type, region_name=region_name) - self.neutron = self.session['neutron'] = neClient.Client('2.0', session=sess, - endpoint_type=self.endpoint_type, - region_name=region_name) - self.cinder = self.session['cinder'] = cClient.Client(2, session=sess, endpoint_type=self.endpoint_type, - region_name=region_name) + self.nova = self.session["nova"] = nClient.Client( + str(version), + session=sess, + endpoint_type=self.endpoint_type, + region_name=region_name, + ) + self.neutron = self.session["neutron"] = neClient.Client( + "2.0", + session=sess, + endpoint_type=self.endpoint_type, + region_name=region_name, + ) + self.cinder = self.session["cinder"] = cClient.Client( + 2, + session=sess, + endpoint_type=self.endpoint_type, + region_name=region_name, + ) + try: - self.my_tenant_id = self.session['my_tenant_id'] = sess.get_project_id() + self.my_tenant_id = self.session["my_tenant_id"] = sess.get_project_id() except Exception: self.logger.error("Cannot get project_id from session", exc_info=True) + if self.endpoint_type == "internalURL": glance_service_id = self.keystone.services.list(name="glance")[0].id - glance_endpoint = self.keystone.endpoints.list(glance_service_id, interface="internal")[0].url + glance_endpoint = self.keystone.endpoints.list( + glance_service_id, interface="internal" + )[0].url else: glance_endpoint = None - self.glance = self.session['glance'] = glClient.Client(2, session=sess, endpoint=glance_endpoint) + + self.glance = self.session["glance"] = glClient.Client( + 2, session=sess, endpoint=glance_endpoint + ) # using version 1 of glance client in new_image() - # self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess, + # self.glancev1 = self.session["glancev1"] = glClient.Client("1", session=sess, # endpoint=glance_endpoint) - self.session['reload_client'] = False - self.persistent_info['session'] = self.session + self.session["reload_client"] = False + self.persistent_info["session"] = self.session # add availablity zone info inside self.persistent_info self._set_availablity_zones() - self.persistent_info['availability_zone'] = self.availability_zone - self.security_groups_id = None # force to get again security_groups_ids next time they are needed + self.persistent_info["availability_zone"] = self.availability_zone + # force to get again security_groups_ids next time they are needed + self.security_groups_id = None def __net_os2mano(self, net_list_dict): """Transform the net openstack format to mano format @@ -298,10 +398,10 @@ class vimconnector(vimconn.VimConnector): else: raise TypeError("param net_list_dict must be a list or a dictionary") for net in net_list_: - if net.get('provider:network_type') == "vlan": - net['type'] = 'data' + if net.get("provider:network_type") == "vlan": + net["type"] = "data" else: - net['type'] = 'bridge' + net["type"] = "bridge" def __classification_os2mano(self, class_list_dict): """Transform the openstack format (Flow Classifier) to mano format @@ -314,20 +414,20 @@ class vimconnector(vimconn.VimConnector): else: raise TypeError("param class_list_dict must be a list or a dictionary") for classification in class_list_: - id = classification.pop('id') - name = classification.pop('name') - description = classification.pop('description') - project_id = classification.pop('project_id') - tenant_id = classification.pop('tenant_id') + id = classification.pop("id") + name = classification.pop("name") + description = classification.pop("description") + project_id = classification.pop("project_id") + tenant_id = classification.pop("tenant_id") original_classification = copy.deepcopy(classification) classification.clear() - classification['ctype'] = 'legacy_flow_classifier' - classification['definition'] = original_classification - classification['id'] = id - classification['name'] = name - classification['description'] = description - classification['project_id'] = project_id - classification['tenant_id'] = tenant_id + classification["ctype"] = "legacy_flow_classifier" + classification["definition"] = original_classification + classification["id"] = id + classification["name"] = name + classification["description"] = description + classification["project_id"] = project_id + classification["tenant_id"] = tenant_id def __sfi_os2mano(self, sfi_list_dict): """Transform the openstack format (Port Pair) to mano format (SFI) @@ -338,25 +438,31 @@ class vimconnector(vimconn.VimConnector): elif isinstance(sfi_list_dict, list): sfi_list_ = sfi_list_dict else: - raise TypeError( - "param sfi_list_dict must be a list or a dictionary") + raise TypeError("param sfi_list_dict must be a list or a dictionary") + for sfi in sfi_list_: - sfi['ingress_ports'] = [] - sfi['egress_ports'] = [] - if sfi.get('ingress'): - sfi['ingress_ports'].append(sfi['ingress']) - if sfi.get('egress'): - sfi['egress_ports'].append(sfi['egress']) - del sfi['ingress'] - del sfi['egress'] - params = sfi.get('service_function_parameters') + sfi["ingress_ports"] = [] + sfi["egress_ports"] = [] + + if sfi.get("ingress"): + sfi["ingress_ports"].append(sfi["ingress"]) + + if sfi.get("egress"): + sfi["egress_ports"].append(sfi["egress"]) + + del sfi["ingress"] + del sfi["egress"] + params = sfi.get("service_function_parameters") sfc_encap = False + if params: - correlation = params.get('correlation') + correlation = params.get("correlation") + if correlation: sfc_encap = True - sfi['sfc_encap'] = sfc_encap - del sfi['service_function_parameters'] + + sfi["sfc_encap"] = sfc_encap + del sfi["service_function_parameters"] def __sf_os2mano(self, sf_list_dict): """Transform the openstack format (Port Pair Group) to mano format (SF) @@ -367,12 +473,12 @@ class vimconnector(vimconn.VimConnector): elif isinstance(sf_list_dict, list): sf_list_ = sf_list_dict else: - raise TypeError( - "param sf_list_dict must be a list or a dictionary") + raise TypeError("param sf_list_dict must be a list or a dictionary") + for sf in sf_list_: - del sf['port_pair_group_parameters'] - sf['sfis'] = sf['port_pairs'] - del sf['port_pairs'] + del sf["port_pair_group_parameters"] + sf["sfis"] = sf["port_pairs"] + del sf["port_pairs"] def __sfp_os2mano(self, sfp_list_dict): """Transform the openstack format (Port Chain) to mano format (SFP) @@ -383,19 +489,22 @@ class vimconnector(vimconn.VimConnector): elif isinstance(sfp_list_dict, list): sfp_list_ = sfp_list_dict else: - raise TypeError( - "param sfp_list_dict must be a list or a dictionary") + raise TypeError("param sfp_list_dict must be a list or a dictionary") + for sfp in sfp_list_: - params = sfp.pop('chain_parameters') + params = sfp.pop("chain_parameters") sfc_encap = False + if params: - correlation = params.get('correlation') + correlation = params.get("correlation") + if correlation: sfc_encap = True - sfp['sfc_encap'] = sfc_encap - sfp['spi'] = sfp.pop('chain_id') - sfp['classifications'] = sfp.pop('flow_classifiers') - sfp['service_functions'] = sfp.pop('port_pair_groups') + + sfp["sfc_encap"] = sfc_encap + sfp["spi"] = sfp.pop("chain_id") + sfp["classifications"] = sfp.pop("flow_classifiers") + sfp["service_functions"] = sfp.pop("port_pair_groups") # placeholder for now; read TODO note below def _validate_classification(self, type, definition): @@ -408,27 +517,72 @@ class vimconnector(vimconn.VimConnector): def _format_exception(self, exception): """Transform a keystone, nova, neutron exception into a vimconn exception discovering the cause""" - message_error = str(exception) - - if isinstance(exception, (neExceptions.NetworkNotFoundClient, nvExceptions.NotFound, ksExceptions.NotFound, - gl1Exceptions.HTTPNotFound)): - raise vimconn.VimConnNotFoundException(type(exception).__name__ + ": " + message_error) - elif isinstance(exception, (HTTPException, gl1Exceptions.HTTPException, gl1Exceptions.CommunicationError, - ConnectionError, ksExceptions.ConnectionError, neExceptions.ConnectionFailed)): - raise vimconn.VimConnConnectionException(type(exception).__name__ + ": " + message_error) - elif isinstance(exception, (KeyError, nvExceptions.BadRequest, ksExceptions.BadRequest)): - raise vimconn.VimConnException(type(exception).__name__ + ": " + message_error) - elif isinstance(exception, (nvExceptions.ClientException, ksExceptions.ClientException, - neExceptions.NeutronException)): - raise vimconn.VimConnUnexpectedResponse(type(exception).__name__ + ": " + message_error) + tip = "" + + if isinstance( + exception, + ( + neExceptions.NetworkNotFoundClient, + nvExceptions.NotFound, + ksExceptions.NotFound, + gl1Exceptions.HTTPNotFound, + ), + ): + raise vimconn.VimConnNotFoundException( + type(exception).__name__ + ": " + message_error + ) + elif isinstance( + exception, + ( + HTTPException, + gl1Exceptions.HTTPException, + gl1Exceptions.CommunicationError, + ConnectionError, + ksExceptions.ConnectionError, + neExceptions.ConnectionFailed, + ), + ): + if type(exception).__name__ == "SSLError": + tip = " (maybe option 'insecure' must be added to the VIM)" + + raise vimconn.VimConnConnectionException( + "Invalid URL or credentials{}: {}".format(tip, message_error) + ) + elif isinstance( + exception, + ( + KeyError, + nvExceptions.BadRequest, + ksExceptions.BadRequest, + ), + ): + raise vimconn.VimConnException( + type(exception).__name__ + ": " + message_error + ) + elif isinstance( + exception, + ( + nvExceptions.ClientException, + ksExceptions.ClientException, + neExceptions.NeutronException, + ), + ): + raise vimconn.VimConnUnexpectedResponse( + type(exception).__name__ + ": " + message_error + ) elif isinstance(exception, nvExceptions.Conflict): - raise vimconn.VimConnConflictException(type(exception).__name__ + ": " + message_error) + raise vimconn.VimConnConflictException( + type(exception).__name__ + ": " + message_error + ) elif isinstance(exception, vimconn.VimConnException): raise exception else: # () self.logger.error("General Exception " + message_error, exc_info=True) - raise vimconn.VimConnConnectionException(type(exception).__name__ + ": " + message_error) + + raise vimconn.VimConnConnectionException( + type(exception).__name__ + ": " + message_error + ) def _get_ids_from_name(self): """ @@ -437,22 +591,32 @@ class vimconnector(vimconn.VimConnector): """ # get tenant_id if only tenant_name is supplied self._reload_connection() + if not self.my_tenant_id: - raise vimconn.VimConnConnectionException("Error getting tenant information from name={} id={}". - format(self.tenant_name, self.tenant_id)) - if self.config.get('security_groups') and not self.security_groups_id: + raise vimconn.VimConnConnectionException( + "Error getting tenant information from name={} id={}".format( + self.tenant_name, self.tenant_id + ) + ) + + if self.config.get("security_groups") and not self.security_groups_id: # convert from name to id - neutron_sg_list = self.neutron.list_security_groups(tenant_id=self.my_tenant_id)["security_groups"] + neutron_sg_list = self.neutron.list_security_groups( + tenant_id=self.my_tenant_id + )["security_groups"] self.security_groups_id = [] - for sg in self.config.get('security_groups'): + for sg in self.config.get("security_groups"): for neutron_sg in neutron_sg_list: if sg in (neutron_sg["id"], neutron_sg["name"]): self.security_groups_id.append(neutron_sg["id"]) break else: self.security_groups_id = None - raise vimconn.VimConnConnectionException("Not found security group {} for this tenant".format(sg)) + + raise vimconn.VimConnConnectionException( + "Not found security group {} for this tenant".format(sg) + ) def check_vim_connectivity(self): # just get network list to check connectivity and credentials @@ -467,51 +631,88 @@ class vimconnector(vimconn.VimConnector): Returns the tenant list of dictionaries: [{'name':', 'id':', ...}, ...] """ self.logger.debug("Getting tenants from VIM filter: '%s'", str(filter_dict)) + try: self._reload_connection() + if self.api_version3: - project_class_list = self.keystone.projects.list(name=filter_dict.get("name")) + project_class_list = self.keystone.projects.list( + name=filter_dict.get("name") + ) else: project_class_list = self.keystone.tenants.findall(**filter_dict) + project_list = [] + for project in project_class_list: - if filter_dict.get('id') and filter_dict["id"] != project.id: + if filter_dict.get("id") and filter_dict["id"] != project.id: continue + project_list.append(project.to_dict()) + return project_list - except (ksExceptions.ConnectionError, ksExceptions.ClientException, ConnectionError) as e: + except ( + ksExceptions.ConnectionError, + ksExceptions.ClientException, + ConnectionError, + ) as e: self._format_exception(e) def new_tenant(self, tenant_name, tenant_description): """Adds a new tenant to openstack VIM. Returns the tenant identifier""" self.logger.debug("Adding a new tenant name: %s", tenant_name) + try: self._reload_connection() + if self.api_version3: - project = self.keystone.projects.create(tenant_name, self.config.get("project_domain_id", "default"), - description=tenant_description, is_domain=False) + project = self.keystone.projects.create( + tenant_name, + self.config.get("project_domain_id", "default"), + description=tenant_description, + is_domain=False, + ) else: project = self.keystone.tenants.create(tenant_name, tenant_description) + return project.id - except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.BadRequest, ConnectionError)\ - as e: + except ( + ksExceptions.ConnectionError, + ksExceptions.ClientException, + ksExceptions.BadRequest, + ConnectionError, + ) as e: self._format_exception(e) def delete_tenant(self, tenant_id): """Delete a tenant from openstack VIM. Returns the old tenant identifier""" self.logger.debug("Deleting tenant %s from VIM", tenant_id) + try: self._reload_connection() + if self.api_version3: self.keystone.projects.delete(tenant_id) else: self.keystone.tenants.delete(tenant_id) + return tenant_id - except (ksExceptions.ConnectionError, ksExceptions.ClientException, ksExceptions.NotFound, ConnectionError)\ - as e: + except ( + ksExceptions.ConnectionError, + ksExceptions.ClientException, + ksExceptions.NotFound, + ConnectionError, + ) as e: self._format_exception(e) - def new_network(self, net_name, net_type, ip_profile=None, shared=False, provider_network_profile=None): + def new_network( + self, + net_name, + net_type, + ip_profile=None, + shared=False, + provider_network_profile=None, + ): """Adds a tenant network to VIM Params: 'net_name': name of the network @@ -536,125 +737,198 @@ class vimconnector(vimconn.VimConnector): Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same as not present. """ - self.logger.debug("Adding a new network to VIM name '%s', type '%s'", net_name, net_type) + self.logger.debug( + "Adding a new network to VIM name '%s', type '%s'", net_name, net_type + ) # self.logger.debug(">>>>>>>>>>>>>>>>>> IP profile %s", str(ip_profile)) try: vlan = None + if provider_network_profile: vlan = provider_network_profile.get("segmentation-id") + new_net = None created_items = {} self._reload_connection() - network_dict = {'name': net_name, 'admin_state_up': True} + network_dict = {"name": net_name, "admin_state_up": True} + if net_type in ("data", "ptp"): provider_physical_network = None - if provider_network_profile and provider_network_profile.get("physical-network"): - provider_physical_network = provider_network_profile.get("physical-network") + + if provider_network_profile and provider_network_profile.get( + "physical-network" + ): + provider_physical_network = provider_network_profile.get( + "physical-network" + ) + # provider-network must be one of the dataplane_physcial_netowrk if this is a list. If it is string # or not declared, just ignore the checking - if isinstance(self.config.get('dataplane_physical_net'), (tuple, list)) and \ - provider_physical_network not in self.config['dataplane_physical_net']: + if ( + isinstance( + self.config.get("dataplane_physical_net"), (tuple, list) + ) + and provider_physical_network + not in self.config["dataplane_physical_net"] + ): raise vimconn.VimConnConflictException( - "Invalid parameter 'provider-network:physical-network' for network creation. '{}' is not " - "one of the declared list at VIM_config:dataplane_physical_net".format( - provider_physical_network)) - if not provider_physical_network: # use the default dataplane_physical_net - provider_physical_network = self.config.get('dataplane_physical_net') + "Invalid parameter 'provider-network:physical-network' " + "for network creation. '{}' is not one of the declared " + "list at VIM_config:dataplane_physical_net".format( + provider_physical_network + ) + ) + + # use the default dataplane_physical_net + if not provider_physical_network: + provider_physical_network = self.config.get( + "dataplane_physical_net" + ) + # if it is non empty list, use the first value. If it is a string use the value directly - if isinstance(provider_physical_network, (tuple, list)) and provider_physical_network: + if ( + isinstance(provider_physical_network, (tuple, list)) + and provider_physical_network + ): provider_physical_network = provider_physical_network[0] if not provider_physical_network: - raise vimconn.VimConnConflictException("You must provide a 'dataplane_physical_net' at VIM_config " - "for creating underlay networks. or use the NS instantiation" - " parameter provider-network:physical-network for the VLD") - - if not self.config.get('multisegment_support'): - network_dict["provider:physical_network"] = provider_physical_network - if provider_network_profile and "network-type" in provider_network_profile: - network_dict["provider:network_type"] = provider_network_profile["network-type"] + raise vimconn.VimConnConflictException( + "missing information needed for underlay networks. Provide " + "'dataplane_physical_net' configuration at VIM or use the NS " + "instantiation parameter 'provider-network.physical-network'" + " for the VLD" + ) + + if not self.config.get("multisegment_support"): + network_dict[ + "provider:physical_network" + ] = provider_physical_network + + if ( + provider_network_profile + and "network-type" in provider_network_profile + ): + network_dict[ + "provider:network_type" + ] = provider_network_profile["network-type"] else: - network_dict["provider:network_type"] = self.config.get('dataplane_network_type', 'vlan') + network_dict["provider:network_type"] = self.config.get( + "dataplane_network_type", "vlan" + ) + if vlan: network_dict["provider:segmentation_id"] = vlan else: # Multi-segment case segment_list = [] segment1_dict = { - "provider:physical_network": '', - "provider:network_type": 'vxlan' + "provider:physical_network": "", + "provider:network_type": "vxlan", } segment_list.append(segment1_dict) segment2_dict = { "provider:physical_network": provider_physical_network, - "provider:network_type": "vlan" + "provider:network_type": "vlan", } + if vlan: segment2_dict["provider:segmentation_id"] = vlan - elif self.config.get('multisegment_vlan_range'): + elif self.config.get("multisegment_vlan_range"): vlanID = self._generate_multisegment_vlanID() segment2_dict["provider:segmentation_id"] = vlanID + # else # raise vimconn.VimConnConflictException( - # "You must provide 'multisegment_vlan_range' at config dict before creating a multisegment + # "You must provide "multisegment_vlan_range" at config dict before creating a multisegment # network") segment_list.append(segment2_dict) network_dict["segments"] = segment_list # VIO Specific Changes. It needs a concrete VLAN if self.vim_type == "VIO" and vlan is None: - if self.config.get('dataplane_net_vlan_range') is None: + if self.config.get("dataplane_net_vlan_range") is None: raise vimconn.VimConnConflictException( - "You must provide 'dataplane_net_vlan_range' in format [start_ID - end_ID] at VIM_config " - "for creating underlay networks") + "You must provide 'dataplane_net_vlan_range' in format " + "[start_ID - end_ID] at VIM_config for creating underlay " + "networks" + ) + network_dict["provider:segmentation_id"] = self._generate_vlanID() network_dict["shared"] = shared + if self.config.get("disable_network_port_security"): network_dict["port_security_enabled"] = False - new_net = self.neutron.create_network({'network': network_dict}) + + if self.config.get("neutron_availability_zone_hints"): + hints = self.config.get("neutron_availability_zone_hints") + + if isinstance(hints, str): + hints = [hints] + + network_dict["availability_zone_hints"] = hints + + new_net = self.neutron.create_network({"network": network_dict}) # print new_net # create subnetwork, even if there is no profile + if not ip_profile: ip_profile = {} - if not ip_profile.get('subnet_address'): + + if not ip_profile.get("subnet_address"): # Fake subnet is required subnet_rand = random.randint(0, 255) - ip_profile['subnet_address'] = "192.168.{}.0/24".format(subnet_rand) - if 'ip_version' not in ip_profile: - ip_profile['ip_version'] = "IPv4" - subnet = {"name": net_name+"-subnet", - "network_id": new_net["network"]["id"], - "ip_version": 4 if ip_profile['ip_version'] == "IPv4" else 6, - "cidr": ip_profile['subnet_address'] - } + ip_profile["subnet_address"] = "192.168.{}.0/24".format(subnet_rand) + + if "ip_version" not in ip_profile: + ip_profile["ip_version"] = "IPv4" + + subnet = { + "name": net_name + "-subnet", + "network_id": new_net["network"]["id"], + "ip_version": 4 if ip_profile["ip_version"] == "IPv4" else 6, + "cidr": ip_profile["subnet_address"], + } + # Gateway should be set to None if not needed. Otherwise openstack assigns one by default - if ip_profile.get('gateway_address'): - subnet['gateway_ip'] = ip_profile['gateway_address'] + if ip_profile.get("gateway_address"): + subnet["gateway_ip"] = ip_profile["gateway_address"] else: - subnet['gateway_ip'] = None - if ip_profile.get('dns_address'): - subnet['dns_nameservers'] = ip_profile['dns_address'].split(";") - if 'dhcp_enabled' in ip_profile: - subnet['enable_dhcp'] = False if \ - ip_profile['dhcp_enabled'] == "false" or ip_profile['dhcp_enabled'] is False else True - if ip_profile.get('dhcp_start_address'): - subnet['allocation_pools'] = [] - subnet['allocation_pools'].append(dict()) - subnet['allocation_pools'][0]['start'] = ip_profile['dhcp_start_address'] - if ip_profile.get('dhcp_count'): - # parts = ip_profile['dhcp_start_address'].split('.') + subnet["gateway_ip"] = None + + if ip_profile.get("dns_address"): + subnet["dns_nameservers"] = ip_profile["dns_address"].split(";") + + if "dhcp_enabled" in ip_profile: + subnet["enable_dhcp"] = ( + False + if ip_profile["dhcp_enabled"] == "false" + or ip_profile["dhcp_enabled"] is False + else True + ) + + if ip_profile.get("dhcp_start_address"): + subnet["allocation_pools"] = [] + subnet["allocation_pools"].append(dict()) + subnet["allocation_pools"][0]["start"] = ip_profile[ + "dhcp_start_address" + ] + + if ip_profile.get("dhcp_count"): + # parts = ip_profile["dhcp_start_address"].split(".") # ip_int = (int(parts[0]) << 24) + (int(parts[1]) << 16) + (int(parts[2]) << 8) + int(parts[3]) - ip_int = int(netaddr.IPAddress(ip_profile['dhcp_start_address'])) - ip_int += ip_profile['dhcp_count'] - 1 + ip_int = int(netaddr.IPAddress(ip_profile["dhcp_start_address"])) + ip_int += ip_profile["dhcp_count"] - 1 ip_str = str(netaddr.IPAddress(ip_int)) - subnet['allocation_pools'][0]['end'] = ip_str + subnet["allocation_pools"][0]["end"] = ip_str + # self.logger.debug(">>>>>>>>>>>>>>>>>> Subnet: %s", str(subnet)) self.neutron.create_subnet({"subnet": subnet}) - if net_type == "data" and self.config.get('multisegment_support'): - if self.config.get('l2gw_support'): + if net_type == "data" and self.config.get("multisegment_support"): + if self.config.get("l2gw_support"): l2gw_list = self.neutron.list_l2_gateways().get("l2_gateways", ()) for l2gw in l2gw_list: l2gw_conn = { @@ -662,22 +936,36 @@ class vimconnector(vimconn.VimConnector): "network_id": new_net["network"]["id"], "segmentation_id": str(vlanID), } - new_l2gw_conn = self.neutron.create_l2_gateway_connection({"l2_gateway_connection": l2gw_conn}) - created_items["l2gwconn:" + str(new_l2gw_conn["l2_gateway_connection"]["id"])] = True + new_l2gw_conn = self.neutron.create_l2_gateway_connection( + {"l2_gateway_connection": l2gw_conn} + ) + created_items[ + "l2gwconn:" + + str(new_l2gw_conn["l2_gateway_connection"]["id"]) + ] = True + return new_net["network"]["id"], created_items except Exception as e: # delete l2gw connections (if any) before deleting the network for k, v in created_items.items(): if not v: # skip already deleted continue + try: k_item, _, k_id = k.partition(":") + if k_item == "l2gwconn": self.neutron.delete_l2_gateway_connection(k_id) except Exception as e2: - self.logger.error("Error deleting l2 gateway connection: {}: {}".format(type(e2).__name__, e2)) + self.logger.error( + "Error deleting l2 gateway connection: {}: {}".format( + type(e2).__name__, e2 + ) + ) + if new_net: - self.neutron.delete_network(new_net['network']['id']) + self.neutron.delete_network(new_net["network"]["id"]) + self._format_exception(e) def get_network_list(self, filter_dict={}): @@ -692,17 +980,26 @@ class vimconnector(vimconn.VimConnector): Returns the network list of dictionaries """ self.logger.debug("Getting network from VIM filter: '%s'", str(filter_dict)) + try: self._reload_connection() filter_dict_os = filter_dict.copy() + if self.api_version3 and "tenant_id" in filter_dict_os: - filter_dict_os['project_id'] = filter_dict_os.pop('tenant_id') # TODO check + # TODO check + filter_dict_os["project_id"] = filter_dict_os.pop("tenant_id") + net_dict = self.neutron.list_networks(**filter_dict_os) net_list = net_dict["networks"] self.__net_os2mano(net_list) + return net_list - except (neExceptions.ConnectionFailed, ksExceptions.ClientException, neExceptions.NeutronException, - ConnectionError) as e: + except ( + neExceptions.ConnectionFailed, + ksExceptions.ClientException, + neExceptions.NeutronException, + ConnectionError, + ) as e: self._format_exception(e) def get_network(self, net_id): @@ -711,24 +1008,36 @@ class vimconnector(vimconn.VimConnector): self.logger.debug(" Getting tenant network %s from VIM", net_id) filter_dict = {"id": net_id} net_list = self.get_network_list(filter_dict) + if len(net_list) == 0: - raise vimconn.VimConnNotFoundException("Network '{}' not found".format(net_id)) + raise vimconn.VimConnNotFoundException( + "Network '{}' not found".format(net_id) + ) elif len(net_list) > 1: - raise vimconn.VimConnConflictException("Found more than one network with this criteria") + raise vimconn.VimConnConflictException( + "Found more than one network with this criteria" + ) + net = net_list[0] subnets = [] for subnet_id in net.get("subnets", ()): try: subnet = self.neutron.show_subnet(subnet_id) except Exception as e: - self.logger.error("osconnector.get_network(): Error getting subnet %s %s" % (net_id, str(e))) + self.logger.error( + "osconnector.get_network(): Error getting subnet %s %s" + % (net_id, str(e)) + ) subnet = {"id": subnet_id, "fault": str(e)} + subnets.append(subnet) + net["subnets"] = subnets - net["encapsulation"] = net.get('provider:network_type') - net["encapsulation_type"] = net.get('provider:network_type') - net["segmentation_id"] = net.get('provider:segmentation_id') - net["encapsulation_id"] = net.get('provider:segmentation_id') + net["encapsulation"] = net.get("provider:network_type") + net["encapsulation_type"] = net.get("provider:network_type") + net["segmentation_id"] = net.get("provider:segmentation_id") + net["encapsulation_id"] = net.get("provider:segmentation_id") + return net def delete_network(self, net_id, created_items=None): @@ -739,130 +1048,182 @@ class vimconnector(vimconn.VimConnector): Returns the network identifier or raises an exception upon error or when network is not found """ self.logger.debug("Deleting network '%s' from VIM", net_id) + if created_items is None: created_items = {} + try: self._reload_connection() # delete l2gw connections (if any) before deleting the network for k, v in created_items.items(): if not v: # skip already deleted continue + try: k_item, _, k_id = k.partition(":") if k_item == "l2gwconn": self.neutron.delete_l2_gateway_connection(k_id) except Exception as e: - self.logger.error("Error deleting l2 gateway connection: {}: {}".format(type(e).__name__, e)) + self.logger.error( + "Error deleting l2 gateway connection: {}: {}".format( + type(e).__name__, e + ) + ) + # delete VM ports attached to this networks before the network ports = self.neutron.list_ports(network_id=net_id) - for p in ports['ports']: + for p in ports["ports"]: try: self.neutron.delete_port(p["id"]) except Exception as e: self.logger.error("Error deleting port %s: %s", p["id"], str(e)) + self.neutron.delete_network(net_id) + return net_id - except (neExceptions.ConnectionFailed, neExceptions.NetworkNotFoundClient, neExceptions.NeutronException, - ksExceptions.ClientException, neExceptions.NeutronException, ConnectionError) as e: + except ( + neExceptions.ConnectionFailed, + neExceptions.NetworkNotFoundClient, + neExceptions.NeutronException, + ksExceptions.ClientException, + neExceptions.NeutronException, + ConnectionError, + ) as e: self._format_exception(e) def refresh_nets_status(self, net_list): """Get the status of the networks - Params: the list of network identifiers - Returns a dictionary with: - net_id: #VIM id of this network - status: #Mandatory. Text with one of: - # DELETED (not found at vim) - # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) - # OTHER (Vim reported other status not understood) - # ERROR (VIM indicates an ERROR status) - # ACTIVE, INACTIVE, DOWN (admin down), - # BUILD (on building process) - # - error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR - vim_info: #Text with plain information obtained from vim (yaml.safe_dump) - + Params: the list of network identifiers + Returns a dictionary with: + net_id: #VIM id of this network + status: #Mandatory. Text with one of: + # DELETED (not found at vim) + # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) + # OTHER (Vim reported other status not understood) + # ERROR (VIM indicates an ERROR status) + # ACTIVE, INACTIVE, DOWN (admin down), + # BUILD (on building process) + # + error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR + vim_info: #Text with plain information obtained from vim (yaml.safe_dump) """ net_dict = {} + for net_id in net_list: net = {} + try: net_vim = self.get_network(net_id) - if net_vim['status'] in netStatus2manoFormat: - net["status"] = netStatus2manoFormat[net_vim['status']] + + if net_vim["status"] in netStatus2manoFormat: + net["status"] = netStatus2manoFormat[net_vim["status"]] else: net["status"] = "OTHER" - net["error_msg"] = "VIM status reported " + net_vim['status'] + net["error_msg"] = "VIM status reported " + net_vim["status"] - if net['status'] == "ACTIVE" and not net_vim['admin_state_up']: - net['status'] = 'DOWN' + if net["status"] == "ACTIVE" and not net_vim["admin_state_up"]: + net["status"] = "DOWN" - net['vim_info'] = self.serialize(net_vim) + net["vim_info"] = self.serialize(net_vim) - if net_vim.get('fault'): # TODO - net['error_msg'] = str(net_vim['fault']) + if net_vim.get("fault"): # TODO + net["error_msg"] = str(net_vim["fault"]) except vimconn.VimConnNotFoundException as e: self.logger.error("Exception getting net status: %s", str(e)) - net['status'] = "DELETED" - net['error_msg'] = str(e) + net["status"] = "DELETED" + net["error_msg"] = str(e) except vimconn.VimConnException as e: self.logger.error("Exception getting net status: %s", str(e)) - net['status'] = "VIM_ERROR" - net['error_msg'] = str(e) + net["status"] = "VIM_ERROR" + net["error_msg"] = str(e) net_dict[net_id] = net return net_dict def get_flavor(self, flavor_id): """Obtain flavor details from the VIM. Returns the flavor dict details""" self.logger.debug("Getting flavor '%s'", flavor_id) + try: self._reload_connection() flavor = self.nova.flavors.find(id=flavor_id) # TODO parse input and translate to VIM format (openmano_schemas.new_vminstance_response_schema) + return flavor.to_dict() - except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, - ConnectionError) as e: + except ( + nvExceptions.NotFound, + nvExceptions.ClientException, + ksExceptions.ClientException, + ConnectionError, + ) as e: self._format_exception(e) def get_flavor_id_from_data(self, flavor_dict): """Obtain flavor id that match the flavor description - Returns the flavor_id or raises a vimconnNotFoundException - flavor_dict: contains the required ram, vcpus, disk - If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus - and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a - vimconnNotFoundException is raised + Returns the flavor_id or raises a vimconnNotFoundException + flavor_dict: contains the required ram, vcpus, disk + If 'use_existing_flavors' is set to True at config, the closer flavor that provides same or more ram, vcpus + and disk is returned. Otherwise a flavor with exactly same ram, vcpus and disk is returned or a + vimconnNotFoundException is raised """ - exact_match = False if self.config.get('use_existing_flavors') else True + exact_match = False if self.config.get("use_existing_flavors") else True + try: self._reload_connection() flavor_candidate_id = None flavor_candidate_data = (10000, 10000, 10000) - flavor_target = (flavor_dict["ram"], flavor_dict["vcpus"], flavor_dict["disk"]) + flavor_target = ( + flavor_dict["ram"], + flavor_dict["vcpus"], + flavor_dict["disk"], + flavor_dict.get("ephemeral", 0), + flavor_dict.get("swap", 0), + ) # numa=None extended = flavor_dict.get("extended", {}) if extended: # TODO - raise vimconn.VimConnNotFoundException("Flavor with EPA still not implemented") + raise vimconn.VimConnNotFoundException( + "Flavor with EPA still not implemented" + ) # if len(numas) > 1: # raise vimconn.VimConnNotFoundException("Cannot find any flavor with more than one numa") # numa=numas[0] # numas = extended.get("numas") for flavor in self.nova.flavors.list(): epa = flavor.get_keys() + if epa: continue # TODO - flavor_data = (flavor.ram, flavor.vcpus, flavor.disk) + + flavor_data = ( + flavor.ram, + flavor.vcpus, + flavor.disk, + flavor.ephemeral, + flavor.swap if isinstance(flavor.swap, int) else 0, + ) if flavor_data == flavor_target: return flavor.id - elif not exact_match and flavor_target < flavor_data < flavor_candidate_data: + elif ( + not exact_match + and flavor_target < flavor_data < flavor_candidate_data + ): flavor_candidate_id = flavor.id flavor_candidate_data = flavor_data + if not exact_match and flavor_candidate_id: return flavor_candidate_id - raise vimconn.VimConnNotFoundException("Cannot find any flavor matching '{}'".format(flavor_dict)) - except (nvExceptions.NotFound, nvExceptions.ClientException, ksExceptions.ClientException, - ConnectionError) as e: + + raise vimconn.VimConnNotFoundException( + "Cannot find any flavor matching '{}'".format(flavor_dict) + ) + except ( + nvExceptions.NotFound, + nvExceptions.ClientException, + ksExceptions.ClientException, + ConnectionError, + ) as e: self._format_exception(e) def process_resource_quota(self, quota, prefix, extra_specs): @@ -871,13 +1232,15 @@ class vimconnector(vimconn.VimConnector): :param extra_specs: :return: """ - if 'limit' in quota: - extra_specs["quota:" + prefix + "_limit"] = quota['limit'] - if 'reserve' in quota: - extra_specs["quota:" + prefix + "_reservation"] = quota['reserve'] - if 'shares' in quota: + if "limit" in quota: + extra_specs["quota:" + prefix + "_limit"] = quota["limit"] + + if "reserve" in quota: + extra_specs["quota:" + prefix + "_reservation"] = quota["reserve"] + + if "shares" in quota: extra_specs["quota:" + prefix + "_shares_level"] = "custom" - extra_specs["quota:" + prefix + "_shares_share"] = quota['shares'] + extra_specs["quota:" + prefix + "_shares_share"] = quota["shares"] def new_flavor(self, flavor_data, change_name_if_used=True): """Adds a tenant flavor to openstack VIM @@ -889,62 +1252,74 @@ class vimconnector(vimconn.VimConnector): retry = 0 max_retries = 3 name_suffix = 0 + try: - name = flavor_data['name'] + name = flavor_data["name"] while retry < max_retries: retry += 1 try: self._reload_connection() + if change_name_if_used: # get used names fl_names = [] fl = self.nova.flavors.list() + for f in fl: fl_names.append(f.name) + while name in fl_names: name_suffix += 1 - name = flavor_data['name']+"-" + str(name_suffix) + name = flavor_data["name"] + "-" + str(name_suffix) - ram = flavor_data.get('ram', 64) - vcpus = flavor_data.get('vcpus', 1) + ram = flavor_data.get("ram", 64) + vcpus = flavor_data.get("vcpus", 1) extra_specs = {} extended = flavor_data.get("extended") if extended: numas = extended.get("numas") + if numas: numa_nodes = len(numas) + if numa_nodes > 1: return -1, "Can not add flavor with more than one numa" + extra_specs["hw:numa_nodes"] = str(numa_nodes) extra_specs["hw:mem_page_size"] = "large" extra_specs["hw:cpu_policy"] = "dedicated" extra_specs["hw:numa_mempolicy"] = "strict" + if self.vim_type == "VIO": - extra_specs["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}' + extra_specs[ + "vmware:extra_config" + ] = '{"numa.nodeAffinity":"0"}' extra_specs["vmware:latency_sensitivity_level"] = "high" + for numa in numas: # overwrite ram and vcpus - # check if key 'memory' is present in numa else use ram value at flavor - if 'memory' in numa: - ram = numa['memory']*1024 + # check if key "memory" is present in numa else use ram value at flavor + if "memory" in numa: + ram = numa["memory"] * 1024 # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/ # implemented/virt-driver-cpu-thread-pinning.html extra_specs["hw:cpu_sockets"] = 1 - if 'paired-threads' in numa: - vcpus = numa['paired-threads']*2 - # cpu_thread_policy "require" implies that the compute node must have an + + if "paired-threads" in numa: + vcpus = numa["paired-threads"] * 2 + # cpu_thread_policy "require" implies that the compute node must have an # STM architecture extra_specs["hw:cpu_thread_policy"] = "require" extra_specs["hw:cpu_policy"] = "dedicated" - elif 'cores' in numa: - vcpus = numa['cores'] - # cpu_thread_policy "prefer" implies that the host must not have an SMT + elif "cores" in numa: + vcpus = numa["cores"] + # cpu_thread_policy "prefer" implies that the host must not have an SMT # architecture, or a non-SMT architecture will be emulated extra_specs["hw:cpu_thread_policy"] = "isolate" extra_specs["hw:cpu_policy"] = "dedicated" - elif 'threads' in numa: - vcpus = numa['threads'] + elif "threads" in numa: + vcpus = numa["threads"] # cpu_thread_policy "prefer" implies that the host may or may not have an SMT # architecture extra_specs["hw:cpu_thread_policy"] = "prefer" @@ -953,45 +1328,91 @@ class vimconnector(vimconn.VimConnector): # if interface["dedicated"]=="yes": # raise vimconn.VimConnException("Passthrough interfaces are not supported # for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable) - # #TODO, add the key 'pci_passthrough:alias"="