From 1ab93ad2bba7ca570d72c4823321169b9f235cf8 Mon Sep 17 00:00:00 2001 From: fmenguy Date: Fri, 4 Jun 2021 14:21:29 +0200 Subject: NFVBENCH-212 Add clouds.yaml file as a config file to use for openstack API access Change-Id: If855ffda1070ed9c9c4544230e4efec185a93f45 Signed-off-by: fmenguy --- nfvbench/cfg.default.yaml | 17 +++++++ nfvbench/cleanup.py | 2 +- nfvbench/credentials.py | 117 +++++++++++++++++++++++++++------------------- nfvbench/nfvbench.py | 10 ++-- test/test_nfvbench.py | 36 ++++++++++++-- 5 files changed, 125 insertions(+), 57 deletions(-) diff --git a/nfvbench/cfg.default.yaml b/nfvbench/cfg.default.yaml index 8e822e5..c76e738 100644 --- a/nfvbench/cfg.default.yaml +++ b/nfvbench/cfg.default.yaml @@ -30,8 +30,25 @@ # - availability_zone # - hypervisor_hostname # - vlans +# WARNING: Not used if clouds_detail is sets openrc_file: +# The OpenStack clouds configuration from clouds.yaml file to use. +# clouds.yaml file must be in one of the following paths: +# - ~/.config/openstack +# - /etc/openstack +# Note: If running in a container, this path must be valid in the container. +# The only case where this field can be empty is when measuring a system that does not run +# OpenStack or when OpenStack APIs are not accessible or OpenStack APis use is not +# desirable. In that case the EXT service chain must be used. +# +# If user is not admin some parameters are mandatory and must be filled with valid values in config file such as : +# - availability_zone +# - hypervisor_hostname +# - vlans +# If a value is sets, this parameter disable the use of openrc file +clouds_detail: + # Forwarder to use in nfvbenchvm image. Available options: ['vpp', 'testpmd'] vm_forwarder: testpmd diff --git a/nfvbench/cleanup.py b/nfvbench/cleanup.py index 23cdf56..cefdcfa 100644 --- a/nfvbench/cleanup.py +++ b/nfvbench/cleanup.py @@ -235,7 +235,7 @@ class Cleaner(object): """Cleaner for all NFVbench resources.""" def __init__(self, config): - cred = credentials.Credentials(config.openrc_file, None, False) + cred = credentials.Credentials(config.openrc_file, config.clouds_detail, None, False) session = cred.get_session() self.neutron_client = nclient.Client('2.0', session=session) self.nova_client = Client(2, session=session) diff --git a/nfvbench/credentials.py b/nfvbench/credentials.py index 4e4985f..a707ba3 100644 --- a/nfvbench/credentials.py +++ b/nfvbench/credentials.py @@ -21,32 +21,40 @@ import getpass from keystoneauth1.identity import v2 from keystoneauth1.identity import v3 from keystoneauth1 import session +import openstack +from keystoneclient.exceptions import HTTPClientError + from .log import LOG class Credentials(object): def get_session(self): - dct = { - 'username': self.rc_username, - 'password': self.rc_password, - 'auth_url': self.rc_auth_url - } - auth = None - - if self.rc_identity_api_version == 3: - dct.update({ - 'project_name': self.rc_project_name, - 'project_domain_name': self.rc_project_domain_name, - 'user_domain_name': self.rc_user_domain_name - }) - auth = v3.Password(**dct) + + if self.clouds_detail: + connection = openstack.connect(cloud=self.clouds_detail) + cred_session = connection.session else: - dct.update({ - 'tenant_name': self.rc_tenant_name - }) - auth = v2.Password(**dct) - return session.Session(auth=auth, verify=self.rc_cacert) + dct = { + 'username': self.rc_username, + 'password': self.rc_password, + 'auth_url': self.rc_auth_url + } + + if self.rc_identity_api_version == 3: + dct.update({ + 'project_name': self.rc_project_name, + 'project_domain_name': self.rc_project_domain_name, + 'user_domain_name': self.rc_user_domain_name + }) + auth = v3.Password(**dct) + else: + dct.update({ + 'tenant_name': self.rc_tenant_name + }) + auth = v2.Password(**dct) + cred_session = session.Session(auth=auth, verify=self.rc_cacert) + return cred_session def __parse_openrc(self, file): export_re = re.compile('export OS_([A-Z_]*)="?(.*)') @@ -91,11 +99,28 @@ class Credentials(object): elif name == "PROJECT_DOMAIN_NAME": self.rc_project_domain_name = value + # /users URL returns exception (HTTP 403) if user is not admin. + # try first without the version in case session already has it in + # Return HTTP 200 if user is admin + def __user_is_admin(self, url): + is_admin = False + try: + # check if user has admin role in OpenStack project + filter = {'service_type': 'identity', + 'interface': 'public'} + self.get_session().get(url, endpoint_filter=filter) + is_admin = True + except HTTPClientError as exc: + if exc.http_status == 403: + LOG.warning( + "User is not admin, no permission to list user roles. Exception: %s", exc) + return is_admin + # # Read a openrc file and take care of the password # The 2 args are passed from the command line and can be None # - def __init__(self, openrc_file, pwd=None, no_env=False): + def __init__(self, openrc_file, clouds_detail, pwd=None, no_env=False): self.rc_password = None self.rc_username = None self.rc_tenant_name = None @@ -105,8 +130,9 @@ class Credentials(object): self.rc_user_domain_name = None self.rc_project_domain_name = None self.rc_project_name = None - self.rc_identity_api_version = 2 + self.rc_identity_api_version = 3 self.is_admin = False + self.clouds_detail = clouds_detail success = True if openrc_file: @@ -118,7 +144,7 @@ class Credentials(object): success = False else: self.__parse_openrc(openrc_file) - elif not no_env: + elif not clouds_detail and not no_env: # no openrc file passed - we assume the variables have been # sourced by the calling shell # just check that they are present @@ -153,34 +179,27 @@ class Credentials(object): # always override with CLI argument if provided - if pwd: - self.rc_password = pwd - # if password not know, check from env variable - elif self.rc_auth_url and not self.rc_password and success: - if 'OS_PASSWORD' in os.environ and not no_env: - self.rc_password = os.environ['OS_PASSWORD'] - else: - # interactively ask for password - self.rc_password = getpass.getpass( - 'Please enter your OpenStack Password: ') - if not self.rc_password: - self.rc_password = "" - - # check if user has admin role in OpenStack project - filter = {'service_type': 'identity', - 'interface': 'public', - 'region_name': self.rc_region_name} + if not clouds_detail: + if pwd: + self.rc_password = pwd + # if password not know, check from env variable + elif self.rc_auth_url and not self.rc_password and success: + if 'OS_PASSWORD' in os.environ and not no_env: + self.rc_password = os.environ['OS_PASSWORD'] + else: + # interactively ask for password + self.rc_password = getpass.getpass( + 'Please enter your OpenStack Password: ') + if not self.rc_password: + self.rc_password = "" + + try: # /users URL returns exception (HTTP 403) if user is not admin. # try first without the version in case session already has it in # Return HTTP 200 if user is admin - self.get_session().get('/users', endpoint_filter=filter) - self.is_admin = True - except Exception: - try: - # vX/users URL returns exception (HTTP 403) if user is not admin. - self.get_session().get('/v' + str(self.rc_identity_api_version) + '/users', - endpoint_filter=filter) - self.is_admin = True - except Exception as e: - LOG.warning("User is not admin, no permission to list user roles. Exception: %s", e) + self.is_admin = self.__user_is_admin('/users') or self.__user_is_admin( + '/v2/users') or self.__user_is_admin('/v3/users') + except Exception as e: + LOG.warning("Error occurred during Openstack API access. " + "Unable to check user is admin. Exception: %s", e) diff --git a/nfvbench/nfvbench.py b/nfvbench/nfvbench.py index 598247a..0719247 100644 --- a/nfvbench/nfvbench.py +++ b/nfvbench/nfvbench.py @@ -60,8 +60,8 @@ class NFVBench(object): self.config_plugin = config_plugin self.factory = factory self.notifier = notifier - self.cred = credentials.Credentials(config.openrc_file, None, False) \ - if config.openrc_file else None + self.cred = credentials.Credentials(config.openrc_file, config.clouds_detail, None, False) \ + if config.openrc_file or config.clouds_detail else None self.chain_runner = None self.specs = Specs() self.specs.set_openstack_spec(openstack_spec) @@ -96,8 +96,10 @@ class NFVBench(object): # check that an empty openrc file (no OpenStack) is only allowed # with EXT chain - if not self.config.openrc_file and self.config.service_chain != ChainType.EXT: - raise Exception("openrc_file in the configuration is required for PVP/PVVP chains") + if (not self.config.openrc_file and not self.config.clouds_detail) and \ + self.config.service_chain != ChainType.EXT: + raise Exception("openrc_file or clouds_detail in the configuration is required" + " for PVP/PVVP chains") self.specs.set_run_spec(self.config_plugin.get_run_spec(self.config, self.specs.openstack)) diff --git a/test/test_nfvbench.py b/test/test_nfvbench.py index e53a586..360e3bd 100644 --- a/test/test_nfvbench.py +++ b/test/test_nfvbench.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. # +import openstack +from keystoneauth1.exceptions import HTTPClientError from mock import patch import pytest @@ -139,13 +141,41 @@ def test_load_from_rate(): # ========================================================================= def test_no_credentials(): - cred = Credentials('/completely/wrong/path/openrc', None, False) - if cred.rc_auth_url: - # shouldn't get valid data unless user set environment variables + with patch.object(openstack, 'connect') as mock: + cred = Credentials('/completely/wrong/path/openrc', None, None, False) + if cred.rc_auth_url: + # shouldn't get valid data unless user set environment variables + assert False + else: + assert True + mock.assert_not_called() + + +def test_clouds_file_credentials(): + with patch.object(openstack, 'connect') as mock: + Credentials(None, 'openstack', None, False) + mock.assert_called_once() + + +@patch('nfvbench.nfvbench.credentials') +def test_is_not_admin(mock_session): + mock_session.Session.return_value.get.return_value.raiseError.side_effect = HTTPClientError + cred = Credentials(None, 'openstack', None, False) + if cred.is_admin: assert False else: assert True + +def test_is_admin(): + with patch.object(openstack, 'connect'): + cred = Credentials(None, 'openstack', None, False) + if cred.is_admin: + assert True + else: + assert False + + def test_ip_block(): ipb = IpBlock('10.0.0.0', '0.0.0.1', 256) assert ipb.get_ip() == '10.0.0.0' -- cgit 1.2.3-korg