Use unique projects for static accounts.
If tests are launched in several threads,
it's possible that the same project is used
in several tests simultaneously.
It causes bad side effects and tests fail.
Patch provides that every static account
has unique project.
Related-PROD: https://mirantis.jira.com/browse/PRODX-19111
Change-Id: Id2e721a33640f4438e2db57b1808e745d506add4
(cherry picked from commit 9471ee64bd346583d8b869624de96fd87df3e89d)
(cherry picked from commit 68e07d7f31df3377e994ae2b731718b23c8c1560)
(cherry picked from commit f26e5d0f93185cd0547f9154b22f29aa89069d70)
(cherry picked from commit 9a67b3773dfa656aabf9bc557a3fb878bf6cfa1f)
Tempest can be aware of pre-provisioned networks
Some tests are against preprovisioned networks if static accounts are used,
e.g.
https://github.com/openstack/tempest/blob/7e96c8e854386f43604ad098a6ec7606ee676145/tempest/api/compute/admin/test_auto_allocate_network.py#L79
So patch makes that tempest takes
projects without networks if tests do not require networks.
If tests require networks then tempest takes projects
with pre-existing networks.
To swithch on the new capability set config option
[auth]separate_projects_by_network_existence to true.
Related-PROD: https://mirantis.jira.com/browse/PRODX-20816
Change-Id: I77dc96ad9f30f3a258dea5b098d3c8090dc2a814
(cherry picked from commit 964330f88c0f269f42a5a816e8141cc1a87f13e9)
(cherry picked from commit f915fb5f45bdba77ad474ba46cea68788e2749a4)
(cherry picked from commit 54702e84c58f4116d6967b54755f130902e134f2)
Added system scope support for static accounts
We have special locking for project scope accounts.
Now we should separate system admin accounts from project accounts.
Related-PROD: https://mirantis.jira.com/browse/PRODX-25578
Change-Id: Id1e5be6dfaa5dd01920074467cbab1255361196c
(cherry picked from commit 0b8bfec57516d7c817414fecb7b1ecf8dbfcaf0e)
(cherry picked from commit 111c762cd7b76b300325a990035f25bd345bf58d)
Combined commit message to support static accounts
Related-prod: PRODX-20816
Change-Id: I6ab001b973aff3db8e6ef9642e18944d4dd64e71
diff --git a/tempest/api/compute/admin/test_auto_allocate_network.py b/tempest/api/compute/admin/test_auto_allocate_network.py
index e8011a6..7cfbeb0 100644
--- a/tempest/api/compute/admin/test_auto_allocate_network.py
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -37,8 +37,6 @@
calls to Neutron to automatically allocate the network topology.
"""
- force_tenant_isolation = True
-
min_microversion = '2.37'
max_microversion = 'latest'
@@ -53,12 +51,6 @@
'auto-allocated-topology extension is not available')
@classmethod
- def setup_credentials(cls):
- # Do not create network resources for these tests.
- cls.set_network_resources()
- super(AutoAllocateNetworkTest, cls).setup_credentials()
-
- @classmethod
def setup_clients(cls):
super(AutoAllocateNetworkTest, cls).setup_clients()
cls.networks_client = cls.os_primary.networks_client
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 449bb90..2e07c80 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -123,8 +123,7 @@
:param name: When provided, it makes it possible to associate credential
artifacts back to the owner (test class).
:param network_resources: Dictionary of network resources to be allocated
- for each test account. Only valid for the dynamic
- credentials provider.
+ for each test account.
:param force_tenant_isolation: Always return a `DynamicCredentialProvider`,
regardless of the configuration.
:param identity_version: Use the specified identity API version, regardless
@@ -145,6 +144,9 @@
# Most params are not relevant for pre-created accounts
return preprov_creds.PreProvisionedCredentialProvider(
name=name,
+ network_resources=network_resources,
+ separate_projects_by_network_existence=(
+ CONF.auth.separate_projects_by_network_existence),
**get_preprov_provider_params(identity_version))
else:
raise exceptions.InvalidConfiguration(
diff --git a/tempest/config.py b/tempest/config.py
index c8954a4..b73b191 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -110,6 +110,14 @@
"This must be set to 'all' if using the "
"[oslo_policy]/enforce_scope=true option for the "
"identity service."),
+ cfg.BoolOpt('separate_projects_by_network_existence',
+ default=False,
+ help="If use_dynamic_credentials is set to False and "
+ "separate_projects_by_network_existence is set to True "
+ "Tempest divides projects with networks and without "
+ "networks. To be compatible with old behavior the config "
+ "option is set to False and project are treated the same "
+ "regardless their network resources.")
]
identity_group = cfg.OptGroup(name='identity',
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index 1583d73..16553fc 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -75,10 +75,13 @@
HASH_CRED_FIELDS = (set(auth.KeystoneV2Credentials.ATTRIBUTES) &
set(auth.KeystoneV3Credentials.ATTRIBUTES))
- def __init__(self, identity_version, test_accounts_file,
- accounts_lock_dir, name=None, credentials_domain=None,
- admin_role=None, object_storage_operator_role=None,
- object_storage_reseller_admin_role=None, identity_uri=None):
+ def __init__(
+ self, identity_version, test_accounts_file, accounts_lock_dir,
+ name=None, credentials_domain=None, admin_role=None,
+ object_storage_operator_role=None,
+ object_storage_reseller_admin_role=None, identity_uri=None,
+ network_resources=None,
+ separate_projects_by_network_existence=False):
super(PreProvisionedCredentialProvider, self).__init__(
identity_version=identity_version, name=name,
admin_role=admin_role, credentials_domain=credentials_domain,
@@ -90,9 +93,12 @@
raise lib_exc.InvalidCredentials("No accounts file specified")
self.hash_dict = self.get_hash_dict(
accounts, admin_role, object_storage_operator_role,
- object_storage_reseller_admin_role)
+ object_storage_reseller_admin_role, network_resources,
+ separate_projects_by_network_existence)
self.accounts_dir = accounts_lock_dir
self._creds = {}
+ self._used_projects_file = os.path.join(self.accounts_dir,
+ 'used_projects')
@classmethod
def _append_role(cls, role, account_hash, hash_dict):
@@ -110,14 +116,21 @@
return hash_dict
@classmethod
- def get_hash_dict(cls, accounts, admin_role,
- object_storage_operator_role=None,
- object_storage_reseller_admin_role=None):
+ def get_hash_dict(
+ cls, accounts, admin_role, object_storage_operator_role=None,
+ object_storage_reseller_admin_role=None, network_resources=None,
+ separate_projects_by_network_existence=False):
hash_dict = {'roles': {}, 'creds': {}, 'networks': {},
'scoped_roles': {}, 'projects': {}}
+ tests_require_projects_with_networks = \
+ cls.do_tests_require_projects_with_networks(network_resources)
# Loop over the accounts read from the yaml file
for account in accounts:
+ if not cls.is_account_needed(
+ tests_require_projects_with_networks, account,
+ separate_projects_by_network_existence):
+ continue
roles = []
types = []
scope = None
@@ -180,7 +193,9 @@
'Unknown resource type %s, ignoring this field',
resource
)
- hash_dict = cls._append_project(account, temp_hash_key, hash_dict)
+ if scope == 'project':
+ hash_dict = cls._append_project(account, temp_hash_key,
+ hash_dict)
return hash_dict
def is_multi_user(self):
@@ -197,19 +212,49 @@
return True
return False
+ def _process_project(self, hash_, used_projects=None, use=True):
+ project = self.hash_dict['creds'][hash_].get('project_name')
+ if not project:
+ project = self.hash_dict['creds'][hash_].get('tenant_name')
+ if not project:
+ return
+ if used_projects is None:
+ used_projects = self._get_used_projects()
+ method = 'append' if use else 'remove'
+ getattr(used_projects, method)(project)
+ with open(self._used_projects_file, 'w') as file:
+ file.write('\n'.join(used_projects) + '\n')
+ return project
+
+ def _get_used_projects(self):
+ used_projects = []
+ try:
+ with open(self._used_projects_file) as file:
+ for line in file:
+ line = line.strip()
+ if line:
+ used_projects.append(line)
+ except FileNotFoundError:
+ pass
+ return used_projects
+
@lockutils.synchronized('test_accounts_io', external=True)
def _get_free_hash(self, hashes):
+ used_projects = self._get_used_projects()
+ hashes = self._exclude_used_projects(hashes, used_projects)
# Cast as a list because in some edge cases a set will be passed in
hashes = list(hashes)
if not os.path.isdir(self.accounts_dir):
os.mkdir(self.accounts_dir)
# Create File from first hash (since none are in use)
self._create_hash_file(hashes[0])
+ self._process_project(hashes[0], used_projects)
return hashes[0]
names = []
for _hash in hashes:
res = self._create_hash_file(_hash)
if res:
+ self._process_project(_hash, used_projects)
return _hash
else:
path = os.path.join(self.accounts_dir, _hash)
@@ -247,7 +292,6 @@
hashes = temp_list
else:
hashes = self.hash_dict['creds'].keys()
- hashes = self._exclude_used_projects(hashes)
# NOTE(mtreinish): admin is a special case because of the increased
# privilege set which could potentially cause issues on tests where
# that is not expected. So unless the admin role isn't specified do
@@ -267,11 +311,14 @@
return temp_creds
def _get_creds(self, roles=None, scope=None):
- useable_hashes = self._get_match_hash_list(roles, scope)
- if not useable_hashes:
+ usable_hashes = self._get_match_hash_list(roles, scope)
+ if not usable_hashes:
msg = 'No users configured for type/roles %s' % roles
raise lib_exc.InvalidCredentials(msg)
- free_hash = self._get_free_hash(useable_hashes)
+ if scope == 'system':
+ free_hash = next(iter(usable_hashes))
+ else:
+ free_hash = self._get_free_hash(usable_hashes)
clean_creds = self._sanitize_creds(
self.hash_dict['creds'][free_hash])
LOG.info('%s allocated creds for roles %s in scope %s:\n%s',
@@ -280,14 +327,15 @@
@lockutils.synchronized('test_accounts_io', external=True)
def remove_hash(self, hash_string):
- hash_path = os.path.join(self.accounts_dir, hash_string)
- if not os.path.isfile(hash_path):
- LOG.warning('Expected an account lock file %s to remove, but '
- 'one did not exist', hash_path)
- else:
- os.remove(hash_path)
- if not os.listdir(self.accounts_dir):
- os.rmdir(self.accounts_dir)
+ if self._process_project(hash_string, use=False):
+ hash_path = os.path.join(self.accounts_dir, hash_string)
+ if not os.path.isfile(hash_path):
+ LOG.warning('Expected an account lock file %s to remove, but '
+ 'one did not exist', hash_path)
+ else:
+ os.remove(hash_path)
+ if not os.listdir(self.accounts_dir):
+ os.rmdir(self.accounts_dir)
def get_hash(self, creds):
for _hash in self.hash_dict['creds']:
@@ -417,7 +465,7 @@
# TODO(gmann): Implement alt reader hash.
return
- def get_creds_by_roles(self, roles, force_new=False, scope=None):
+ def get_creds_by_roles(self, roles, force_new=True, scope=None):
roles = list(set(roles))
exist_creds = self._creds.get(str(roles).encode(
'utf-8'), None)
@@ -502,8 +550,39 @@
hash_dict['projects'][key_to_add].append(account_hash)
return hash_dict
- def _exclude_used_projects(self, hashes):
+ def _exclude_used_projects(self, hashes, used_projects):
excluded_accounts = []
- for project in [cred.tenant_name for cred in self._creds.values()]:
- excluded_accounts.extend(self.hash_dict['projects'][project])
- return hashes - set(excluded_accounts)
+ for project in used_projects:
+ if project in self.hash_dict['projects']:
+ excluded_accounts.extend(self.hash_dict['projects'][project])
+ return set(hashes) - set(excluded_accounts)
+
+ @staticmethod
+ def do_tests_require_projects_with_networks(network_resources):
+ """take projects with networks or projects without networks
+
+ :return: boolean value
+ """
+ if isinstance(network_resources, dict):
+ if any(network_resources.values()):
+ return True
+ return False
+
+ @staticmethod
+ def is_account_needed(tests_require_projects_with_networks, account,
+ separate_projects_by_network_existence=False):
+ """decides whether we need account for test class
+
+ :param tests_require_projects_with_networks: need projects with or
+ without network
+ :param account: dictionary which contains username, password, resources
+ :param separate_projects_by_network_existence: should we separate
+ accounts with and without networks
+ :return: boolean value
+ """
+ if ({'project_name', 'tenant_name'} & account.keys()) and \
+ separate_projects_by_network_existence:
+ is_network_in_resources = 'network' in account.get('resources', {})
+ return is_network_in_resources ==\
+ tests_require_projects_with_networks
+ return True
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 3df9f19..ad7d373 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -492,7 +492,9 @@
self.assertFalse(
verify_tempest_config.contains_version('v5.', ['v1.0', 'v2.0']))
- def test_check_service_availability(self):
+ @mock.patch('tempest.cmd.verify_tempest_config.CONF._config',
+ new_callable=fake_config.FakePrivate)
+ def test_check_service_availability(self, mock_config):
class FakeAuthProvider:
def get_auth(self):
return ('token',
@@ -505,7 +507,6 @@
class Fake_os:
auth_provider = FakeAuthProvider()
auth_version = 'v2'
- verify_tempest_config.CONF._config = fake_config.FakePrivate()
services = verify_tempest_config.check_service_availability(
Fake_os(), True)
self.assertEqual(
diff --git a/tempest/tests/common/test_credentials_factory.py b/tempest/tests/common/test_credentials_factory.py
index 154d8d1..7c4d9e3 100644
--- a/tempest/tests/common/test_credentials_factory.py
+++ b/tempest/tests/common/test_credentials_factory.py
@@ -129,14 +129,14 @@
mock_preprov_provider_params.return_value = expected_params
expected_name = 'my_name'
expected_identity_version = 'identity_version'
- cf.get_credentials_provider(
- expected_name,
- force_tenant_isolation=False,
- identity_version=expected_identity_version)
+ cf.get_credentials_provider(expected_name,
+ identity_version=expected_identity_version)
mock_preprov_provider_params.assert_called_once_with(
expected_identity_version)
mock_preprov_credentials_provider_class.assert_called_once_with(
- name=expected_name, **expected_params)
+ name=expected_name, network_resources=None,
+ separate_projects_by_network_existence=False,
+ **expected_params)
def test_get_credentials_provider_preprov_no_file(self):
cfg.CONF.set_default('use_dynamic_credentials', False, group='auth')
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index 4e39c6b..04e6771 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -80,12 +80,6 @@
'password': 'p', 'types': ['admin']},
{'username': 'test_user14', 'project_name': 'test_tenant14',
'password': 'p', 'roles': ['member']},
- {'username': 'test_project_manager1',
- 'project_name': 'test_tenant14', 'password': 'p',
- 'roles': ['manager']},
- {'username': 'test_project_manager2',
- 'tenant_name': 'test_tenant15', 'password': 'p',
- 'roles': ['manager']},
]
def setUp(self):
@@ -122,6 +116,17 @@
hash_list.append(temp_hash)
return hash_list
+ def _remove_hash(self, hash_list, hash_index):
+ test_account_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ remove_mock = self.useFixture(fixtures.MockPatch('os.remove'))
+ rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir'))
+ test_account_class.remove_hash(hash_list[hash_index])
+ hash_path = os.path.join(self.fixed_params['accounts_lock_dir'],
+ hash_list[hash_index])
+ return {'remove_mock': remove_mock, 'hash_path': hash_path,
+ 'rmdir_mock': rmdir_mock}
+
def test_get_hash(self):
# Test with all accounts to make sure we try all combinations
# and hide no race conditions
@@ -188,8 +193,12 @@
create=True) as open_mock:
test_account_class._get_free_hash(hash_list)
lock_path = os.path.join(self.fixed_params['accounts_lock_dir'],
- hash_list[0])
- open_mock.assert_called_once_with(lock_path, 'w')
+ list(set(hash_list))[0])
+ accounts_lock_dir_calls = \
+ [mock.call(test_account_class._used_projects_file),
+ mock.call(lock_path, 'w'),
+ mock.call(test_account_class._used_projects_file, 'w')]
+ open_mock.assert_has_calls(accounts_lock_dir_calls, any_order=True)
mkdir_path = os.path.join(self.fixed_params['accounts_lock_dir'])
mkdir_mock.mock.assert_called_once_with(mkdir_path)
@@ -230,43 +239,33 @@
hash_list[3])
open_mock.assert_has_calls([mock.call(lock_path, 'w')])
- @mock.patch('oslo_concurrency.lockutils.lock')
- def test_remove_hash_last_account(self, lock_mock):
+ @mock.patch('tempest.lib.common.preprov_creds.'
+ 'PreProvisionedCredentialProvider._process_project')
+ def test_remove_hash_last_account(self, _process_project_mock):
hash_list = self._get_hash_list(self.test_accounts)
# Pretend the pseudo-lock is there
self.useFixture(
fixtures.MockPatch('os.path.isfile', return_value=True))
# Pretend the lock dir is empty
self.useFixture(fixtures.MockPatch('os.listdir', return_value=[]))
- test_account_class = preprov_creds.PreProvisionedCredentialProvider(
- **self.fixed_params)
- remove_mock = self.useFixture(fixtures.MockPatch('os.remove'))
- rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir'))
- test_account_class.remove_hash(hash_list[2])
- hash_path = os.path.join(self.fixed_params['accounts_lock_dir'],
- hash_list[2])
+ result = self._remove_hash(hash_list, 2)
lock_path = self.fixed_params['accounts_lock_dir']
- remove_mock.mock.assert_called_once_with(hash_path)
- rmdir_mock.mock.assert_called_once_with(lock_path)
+ result['remove_mock'].mock.assert_called_once_with(result['hash_path'])
+ result['rmdir_mock'].mock.assert_called_once_with(lock_path)
- @mock.patch('oslo_concurrency.lockutils.lock')
- def test_remove_hash_not_last_account(self, lock_mock):
+ @mock.patch('tempest.lib.common.preprov_creds.'
+ 'PreProvisionedCredentialProvider._process_project')
+ def test_remove_hash_not_last_account(self, _process_project_mock):
hash_list = self._get_hash_list(self.test_accounts)
# Pretend the pseudo-lock is there
self.useFixture(fixtures.MockPatch(
'os.path.isfile', return_value=True))
- # Pretend the lock dir is empty
+ # Pretend the lock dir is not empty
self.useFixture(fixtures.MockPatch('os.listdir', return_value=[
hash_list[1], hash_list[4]]))
- test_account_class = preprov_creds.PreProvisionedCredentialProvider(
- **self.fixed_params)
- remove_mock = self.useFixture(fixtures.MockPatch('os.remove'))
- rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir'))
- test_account_class.remove_hash(hash_list[2])
- hash_path = os.path.join(self.fixed_params['accounts_lock_dir'],
- hash_list[2])
- remove_mock.mock.assert_called_once_with(hash_path)
- rmdir_mock.mock.assert_not_called()
+ result = self._remove_hash(hash_list, 2)
+ result['remove_mock'].mock.assert_called_once_with(result['hash_path'])
+ result['rmdir_mock'].mock.assert_not_called()
def test_is_multi_user(self):
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
@@ -328,7 +327,7 @@
calls = get_free_hash_mock.mock.mock_calls
self.assertEqual(len(calls), 1)
args = calls[0][1][0]
- self.assertEqual(len(args), 13)
+ self.assertEqual(len(args), 11)
for i in admin_hashes:
self.assertNotIn(i, args)
@@ -343,7 +342,7 @@
'tempest.lib.common.preprov_creds.read_accounts_yaml',
return_value=test_accounts))
test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
- **self.fixed_params)
+ network_resources={"test": "test"}, **self.fixed_params)
with mock.patch('tempest.lib.services.network.networks_client.'
'NetworksClient.list_networks',
return_value={'networks': [{'name': 'network-2',
@@ -440,26 +439,6 @@
# Get one more
test_accounts_class.get_admin_creds()
- def test_get_project_manager_creds(self):
- test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
- **self.fixed_params)
- p_manager_creds = test_accounts_class.get_project_manager_creds()
- self.assertNotIn('test_admin', p_manager_creds.username)
- self.assertNotIn('test_user', p_manager_creds.username)
- self.assertIn('test_project_manager', p_manager_creds.username)
-
- def test_get_project_manager_creds_none_available(self):
- admin_accounts = [x for x in self.test_accounts if 'test_admin'
- in x['username']]
- self.useFixture(fixtures.MockPatch(
- 'tempest.lib.common.preprov_creds.read_accounts_yaml',
- return_value=admin_accounts))
- test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
- **self.fixed_params)
- with testtools.ExpectedException(lib_exc.InvalidCredentials):
- # Get one more
- test_accounts_class.get_project_manager_creds()
-
class TestPreProvisionedCredentialsV3(TestPreProvisionedCredentials):
@@ -513,29 +492,4 @@
{'username': 'test_user14', 'project_name': 'test_tenant14',
'domain_name': 'domain', 'password': 'p',
'roles': ['member']},
- {'username': 'test_project_manager1',
- 'project_name': 'test_project14', 'domain_name': 'domain',
- 'password': 'p', 'roles': ['manager']},
- {'username': 'test_domain_manager1',
- 'domain_name': 'domain', 'password': 'p', 'roles': ['manager']},
]
-
- def test_get_domain_manager_creds(self):
- test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
- **self.fixed_params)
- d_manager_creds = test_accounts_class.get_domain_manager_creds()
- self.assertNotIn('test_admin', d_manager_creds.username)
- self.assertNotIn('test_user', d_manager_creds.username)
- self.assertIn('test_domain_manager', d_manager_creds.username)
-
- def test_get_domain_manager_creds_none_available(self):
- admin_accounts = [x for x in self.test_accounts if 'test_admin'
- in x['username']]
- self.useFixture(fixtures.MockPatch(
- 'tempest.lib.common.preprov_creds.read_accounts_yaml',
- return_value=admin_accounts))
- test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
- **self.fixed_params)
- with testtools.ExpectedException(lib_exc.InvalidCredentials):
- # Get one more
- test_accounts_class.get_domain_manager_creds()