Merge "Test only openstack official tempest plugins in plugin sanity job"
diff --git a/releasenotes/notes/support-srbac-in-pre-provisioned-creds-f1b817e85c1fb171.yaml b/releasenotes/notes/support-srbac-in-pre-provisioned-creds-f1b817e85c1fb171.yaml
new file mode 100644
index 0000000..c264cad
--- /dev/null
+++ b/releasenotes/notes/support-srbac-in-pre-provisioned-creds-f1b817e85c1fb171.yaml
@@ -0,0 +1,22 @@
+---
+prelude: >
+ The pre-provisioned credentials supports the new SRBAC personas.
+features:
+ - |
+ Add support for SRBAC personas as well as system scope in the
+ ``tempest account-generator`` command as well as in pre
+ provisioned credentials. The below SRBAC personas are supported:
+
+ * ``system admin``
+ * ``system member``
+ * ``system reader``
+ * ``project manager``
+ * ``project member``
+ * ``project reader``
+fixes:
+ - |
+ Fixed a bug#2143564 in the pre-provisioned credentials provider where
+ ``project_member`` and ``project_reader`` were not guaranteed to be
+ allocated from the same project. The ``PreProvisionedCredentialProvider``
+ now stores accounts by project name and gets the accounts based on the
+ project name if they are requested from the same test class.
diff --git a/tempest/api/compute/floating_ips/base.py b/tempest/api/compute/floating_ips/base.py
index d6c302d..d39e955 100644
--- a/tempest/api/compute/floating_ips/base.py
+++ b/tempest/api/compute/floating_ips/base.py
@@ -41,3 +41,8 @@
def setup_clients(cls):
super(BaseFloatingIPsTest, cls).setup_clients()
cls.client = cls.floating_ips_client
+ if CONF.enforce_scope.nova and hasattr(cls, 'os_project_reader'):
+ cls.reader_floating_ips_client = (
+ cls.os_project_reader.compute_floating_ips_client)
+ else:
+ cls.reader_floating_ips_client = cls.client
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index 6097bbc..8d569c3 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -37,10 +37,11 @@
floating_ip_id_allocated = body['id']
self.addCleanup(self.client.delete_floating_ip,
floating_ip_id_allocated)
- floating_ip_details = self.client.show_floating_ip(
+ floating_ip_details = self.reader_floating_ips_client.show_floating_ip(
floating_ip_id_allocated)['floating_ip']
# Checking if the details of allocated IP is in list of floating IP
- body = self.client.list_floating_ips()['floating_ips']
+ body = self.reader_floating_ips_client.list_floating_ips()[
+ 'floating_ips']
self.assertIn(floating_ip_details, body)
@decorators.idempotent_id('de45e989-b5ca-4a9b-916b-04a52e7bbb8b')
@@ -87,8 +88,8 @@
self.server_id)
# Check instance_id in the floating_ip body
- body = (self.client.show_floating_ip(self.floating_ip_id)
- ['floating_ip'])
+ body = (self.reader_floating_ips_client.show_floating_ip(
+ self.floating_ip_id)['floating_ip'])
self.assertEqual(self.server_id, body['instance_id'])
# Disassociation of floating IP that was associated in this method
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
index e99e218..4e98cbe 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -34,7 +34,8 @@
super(FloatingIPsNegativeTestJSON, cls).resource_setup()
# Generating a nonexistent floatingIP id
- body = cls.client.list_floating_ips()['floating_ips']
+ body = cls.reader_floating_ips_client.list_floating_ips()[
+ 'floating_ips']
floating_ip_ids = [floating_ip['id'] for floating_ip in body]
while True:
if CONF.service_available.neutron:
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index fcbea2f..3eecab3 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -39,7 +39,8 @@
@decorators.idempotent_id('16db31c3-fb85-40c9-bbe2-8cf7b67ff99f')
def test_list_floating_ips(self):
"""Test listing floating ips"""
- body = self.client.list_floating_ips()['floating_ips']
+ body = self.reader_floating_ips_client.list_floating_ips()[
+ 'floating_ips']
floating_ips = body
self.assertNotEmpty(floating_ips,
"Expected floating IPs. Got zero.")
@@ -58,7 +59,8 @@
floating_ip_instance_id = body['instance_id']
floating_ip_ip = body['ip']
floating_ip_fixed_ip = body['fixed_ip']
- body = self.client.show_floating_ip(floating_ip_id)['floating_ip']
+ body = self.reader_floating_ips_client.show_floating_ip(
+ floating_ip_id)['floating_ip']
# Comparing the details of floating IP
self.assertEqual(floating_ip_instance_id,
body['instance_id'])
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
index aa0320d..3919147 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips_negative.py
@@ -41,4 +41,5 @@
else:
non_exist_id = data_utils.rand_int_id(start=999)
self.assertRaises(lib_exc.NotFound,
- self.client.show_floating_ip, non_exist_id)
+ self.reader_floating_ips_client.show_floating_ip,
+ non_exist_id)
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index 031ddee..b0e1f22 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -156,6 +156,8 @@
self.assertEqual(len(container_list), limit)
@decorators.idempotent_id('638f876d-6a43-482a-bbb3-0840bca101c6')
+ @testtools.skipIf(not CONF.auth.use_dynamic_credentials,
+ 'Skipped because of bug#2147232.')
def test_list_containers_with_marker(self):
"""Test listing containers with marker parameter
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index f4f4b17..a7253a1 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -155,12 +155,16 @@
# Create the list of resources to be provisioned for each process
# NOTE(andreaf) get_credentials expects a string for types or a list for
# roles. Adding all required inputs to the spec list.
- spec = ['primary', 'alt', 'project_reader']
+ spec = ['alt', 'project_manager', 'project_member', 'project_reader']
+ if cred_provider.identity_version == 'v3':
+ spec += ['system_member', 'system_reader']
if CONF.service_available.swift:
spec.append([CONF.object_storage.operator_role])
spec.append([CONF.object_storage.reseller_admin_role])
if admin:
spec.append('admin')
+ if cred_provider.identity_version == 'v3':
+ spec.append('system_admin')
resources = []
for cred_type in spec:
scope = None
@@ -175,6 +179,17 @@
def dump_accounts(resources, identity_version, account_file):
accounts = []
+ # NOTE(gmaan): When accounts are created under same project (project
+ # manager, member, and reader), only the first account creation has the
+ # network resource and other accounts will not have it in the generated
+ # account file. To solve that, we need to build a project & network name
+ # map so that all accounts sharing a project get the network resource.
+ project_network_map = {}
+ for _, test_resource in resources:
+ proj = test_resource.project_name or test_resource.tenant_name
+ if proj and test_resource.network:
+ project_network_map.setdefault(proj, test_resource.network['name'])
+
for resource in resources:
cred_type, test_resource = resource
account = {
@@ -182,8 +197,11 @@
'password': test_resource.password
}
if identity_version == 3:
- account['project_name'] = test_resource.project_name
- account['domain_name'] = test_resource.domain_name
+ if test_resource.system:
+ account['system'] = test_resource.system
+ else:
+ account['project_name'] = test_resource.project_name
+ account['domain_name'] = test_resource.domain_name
else:
account['project_name'] = test_resource.tenant_name
@@ -194,9 +212,13 @@
elif cred_type not in ['primary', 'alt']:
account['roles'] = cred_type
+ net_name = None
if test_resource.network:
- account['resources'] = {}
- account['resources']['network'] = test_resource.network['name']
+ net_name = test_resource.network['name']
+ elif test_resource.project_name:
+ net_name = project_network_map.get(test_resource.project_name)
+ if net_name:
+ account['resources'] = {'network': net_name}
accounts.append(account)
if os.path.exists(account_file):
os.rename(account_file, '.'.join((account_file, 'bak')))
diff --git a/tempest/lib/common/cred_provider.py b/tempest/lib/common/cred_provider.py
index 9be7c5e..a20e68d 100644
--- a/tempest/lib/common/cred_provider.py
+++ b/tempest/lib/common/cred_provider.py
@@ -47,6 +47,26 @@
raise exceptions.InvalidIdentityVersion(
identity_version=self.identity_version)
+ def _get_project_id(self, credential_type, scope, return_name=False):
+ same_creds = [['admin'], ['manager'], ['member'], ['reader']]
+ same_alt_creds = [['alt_admin'], ['alt_manager'],
+ ['alt_member'], ['alt_reader']]
+ search_in = []
+ if credential_type in same_creds:
+ search_in = same_creds
+ elif credential_type in same_alt_creds:
+ search_in = same_alt_creds
+ for cred in search_in:
+ found_cred = (self._creds.get("%s_%s" % (scope, str(cred))) or
+ self._creds.get("%s_%s" % (scope, '_'.join(cred))))
+ if found_cred:
+ field = 'name' if return_name else 'id'
+ project_id = found_cred.get("%s_%s" % (scope, field))
+ LOG.debug("Reusing existing project %s from creds: %s ",
+ project_id, found_cred)
+ return project_id
+ return None
+
@abc.abstractmethod
def get_primary_creds(self):
return
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index 11e7215..be98100 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -383,24 +383,6 @@
self.routers_admin_client.add_router_interface(router_id,
subnet_id=subnet_id)
- def _get_project_id(self, credential_type, scope):
- same_creds = [['admin'], ['manager'], ['member'], ['reader']]
- same_alt_creds = [['alt_admin'], ['alt_manager'],
- ['alt_member'], ['alt_reader']]
- search_in = []
- if credential_type in same_creds:
- search_in = same_creds
- elif credential_type in same_alt_creds:
- search_in = same_alt_creds
- for cred in search_in:
- found_cred = self._creds.get("%s_%s" % (scope, str(cred)))
- if found_cred:
- project_id = found_cred.get("%s_%s" % (scope, 'id'))
- LOG.debug("Reusing existing project %s from creds: %s ",
- project_id, found_cred)
- return project_id
- return None
-
def get_credentials(self, credential_type, scope=None, by_role=False):
cred_prefix = ''
if by_role:
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index e685c2c..b52a131 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -114,7 +114,7 @@
object_storage_operator_role=None,
object_storage_reseller_admin_role=None):
hash_dict = {'roles': {}, 'creds': {}, 'networks': {},
- 'scoped_roles': {}}
+ 'scoped_roles': {}, 'project_names': {}}
# Loop over the accounts read from the yaml file
for account in accounts:
@@ -128,7 +128,7 @@
types = account.pop('types')
if 'resources' in account:
resources = account.pop('resources')
- if 'project_name' in account:
+ if 'project_name' in account or 'tenant_name' in account:
scope = 'project'
elif 'domain_name' in account:
scope = 'domain'
@@ -140,9 +140,17 @@
temp_hash.update(str(account_for_hash).encode('utf-8'))
temp_hash_key = temp_hash.hexdigest()
hash_dict['creds'][temp_hash_key] = account
+ if 'project_name' in account or 'tenant_name' in account:
+ field = ('tenant_name' if 'tenant_name' in account else
+ 'project_name')
+ hash_dict['project_names'].setdefault(
+ account[field], [])
+ hash_dict['project_names'][account[field]].append(
+ temp_hash_key)
for role in roles:
- hash_dict = cls._append_role(role, temp_hash_key,
- hash_dict)
+ if not scope or scope != 'system':
+ hash_dict = cls._append_role(role, temp_hash_key,
+ hash_dict)
if scope:
hash_dict = cls._append_scoped_role(
scope, role, temp_hash_key, hash_dict)
@@ -180,6 +188,7 @@
'Unknown resource type %s, ignoring this field',
resource
)
+ LOG.debug('Pre provisioned hash dict: %s', hash_dict)
return hash_dict
def is_multi_user(self):
@@ -218,7 +227,7 @@
'the credentials for this allocation request' % ','.join(names))
raise lib_exc.InvalidCredentials(msg)
- def _get_match_hash_list(self, roles=None, scope=None):
+ def _get_match_hash_list(self, roles=None, scope=None, project_name=None):
hashes = []
if roles:
# Loop over all the creds for each role in the subdict and generate
@@ -238,12 +247,27 @@
"No credentials with role: %s specified in the "
"accounts file" % role)
hashes.append(temp_hashes)
- # Take the list of lists and do a boolean and between each list to
- # find the creds which fall under all the specified roles
+ # Take the list of lists and do a boolean and between each
+ # list to find the creds which fall under all the specified
+ # roles
temp_list = set(hashes[0])
for hash_list in hashes[1:]:
temp_list = temp_list & set(hash_list)
hashes = temp_list
+
+ if project_name:
+ p_hashes = self.hash_dict['project_names'].get(
+ project_name, [])
+ if not p_hashes:
+ raise lib_exc.InvalidCredentials(
+ "No credentials with project_name: %s specified in "
+ "the accounts file" % project_name)
+ hashes = set(hashes) & set(p_hashes)
+ if not hashes:
+ raise lib_exc.InvalidCredentials(
+ "No credentials with project_name: %s, scope %s, and "
+ "role: %s specified in the accounts file" % (
+ project_name, scope, role))
else:
hashes = self.hash_dict['creds'].keys()
# NOTE(mtreinish): admin is a special case because of the increased
@@ -257,6 +281,34 @@
useable_hashes = [x for x in hashes if x not in admin_hashes]
else:
useable_hashes = hashes
+ # (gmaan): When a test requests its first credentials, prefer the
+ # project with the largest set of role accounts. This helps ensure
+ # that subsequent credential requests can be fulfilled from the same
+ # project.
+ if (scope == 'project' and not project_name and
+ roles in [['manager'], ['member'], ['reader'],
+ ['alt_manager'], ['alt_member'], ['alt_reader']]):
+ if roles in [['manager'], ['member'], ['reader']]:
+ personas = ['manager', 'member', 'reader']
+ else:
+ personas = ['alt_manager', 'alt_member', 'alt_reader']
+ scoped_hashes = [
+ set(self.hash_dict['scoped_roles'].get(
+ 'project_%s' % persona, []))
+ for persona in personas
+ ]
+ scored = []
+ for hash in useable_hashes:
+ creds = self.hash_dict['creds'][hash]
+ proj = (creds.get('project_name') or
+ creds.get('tenant_name'))
+ proj_hashes = set(
+ self.hash_dict['project_names'].get(proj, []))
+ score = sum(1 for s in scoped_hashes if proj_hashes & s)
+ scored.append((score, hash))
+ scored.sort(reverse=True)
+ useable_hashes = [hash for _, hash in scored]
+ LOG.info('Pre provisioned useable hashes: %s', useable_hashes)
return useable_hashes
def _sanitize_creds(self, creds):
@@ -265,7 +317,12 @@
return temp_creds
def _get_creds(self, roles=None, scope=None):
- useable_hashes = self._get_match_hash_list(roles, scope)
+ project_name = None
+ if scope == 'project' and roles in [['manager'], ['member'],
+ ['reader'], ['alt_manager'],
+ ['alt_member'], ['alt_reader']]:
+ project_name = self._get_project_id(roles, scope, return_name=True)
+ useable_hashes = self._get_match_hash_list(roles, scope, project_name)
if not useable_hashes:
msg = 'No users configured for type/roles %s' % roles
raise lib_exc.InvalidCredentials(msg)
@@ -310,11 +367,7 @@
# TODO(gmann): Remove this method in favor of get_project_member_creds()
# after the deprecation phase.
def get_primary_creds(self):
- if self._creds.get('primary'):
- return self._creds.get('primary')
- net_creds = self._get_creds()
- self._creds['primary'] = net_creds
- return net_creds
+ return self.get_project_member_creds()
# TODO(gmann): Replace this method with more appropriate name.
# like get_project_alt_member_creds()
@@ -469,11 +522,25 @@
identity_uri=self.identity_uri)
networks_client = net_clients.network.NetworksClient()
net_name = self.hash_dict['networks'].get(hash, None)
+ if not net_name:
+ # NOTE(gmaan): If no network is found for this hash, try to use
+ # the network from another account in the same project, since
+ # project manager/member/reader accounts share the same project
+ # network.
+ project = (creds_dict.get('project_name') or
+ creds_dict.get('tenant_name'))
+ if project:
+ for project_hash in self.hash_dict['project_names'].get(
+ project, []):
+ if project_hash in self.hash_dict['networks']:
+ net_name = self.hash_dict['networks'][project_hash]
+ break
try:
network = fixed_network.get_network_from_name(
net_name, networks_client)
except lib_exc.InvalidTestResource:
network = {}
+ LOG.info('Network %s allocated for hash %s', network, hash)
net_creds.set_resources(network=network)
return net_creds
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index 9647467..30d5b71 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -54,6 +54,9 @@
self.cred_client + '.create_project',
return_value=fake_resource))
self.useFixture(fixtures.MockPatch(
+ self.cred_client + '.show_project',
+ return_value=fake_resource))
+ self.useFixture(fixtures.MockPatch(
self.cred_client + '.assign_user_role'))
self.useFixture(fixtures.MockPatch(
self.cred_client + '._check_role_exists',
@@ -80,6 +83,8 @@
return_value=fake_domain_list))
self.useFixture(fixtures.MockPatch(
self.cred_client + '.assign_user_role_on_domain'))
+ self.useFixture(fixtures.MockPatch(
+ self.cred_client + '.assign_user_role_on_system'))
class TestAccountGeneratorV2(base.TestCase, MockHelpersMixin):
@@ -136,6 +141,10 @@
cred_client = 'tempest.lib.common.cred_client.V2CredsClient'
dynamic_creds = ('tempest.lib.common.dynamic_creds.'
'DynamicCredentialProvider')
+ count_no_admin_no_swift = 4
+ count_admin_no_swift = 5
+ count_swift_admin = 7
+ count_swift_no_admin = 6
def setUp(self):
super(TestGenerateResourcesV2, self).setUp()
@@ -153,21 +162,17 @@
resources = account_generator.generate_resources(
self.cred_provider, admin=False)
resource_types = [k for k, _ in resources]
- # No admin, no swift, expect three credentials only
- self.assertEqual(3, len(resources))
- # Ensure create_user was invoked three times (three distinct users)
- self.assertEqual(3, self.user_create_fixture.mock.call_count)
- self.assertIn('primary', resource_types)
+ self.assertEqual(self.count_no_admin_no_swift, len(resources))
+ self.assertEqual(self.count_no_admin_no_swift,
+ self.user_create_fixture.mock.call_count)
+ self.assertIn(['manager'], resource_types)
+ self.assertIn(['member'], resource_types)
self.assertIn('alt', resource_types)
self.assertNotIn('admin', resource_types)
self.assertIn(['reader'], resource_types)
self.assertNotIn(['fake_operator'], resource_types)
self.assertNotIn(['fake_reseller'], resource_types)
self.assertNotIn(['fake_owner'], resource_types)
- for resource in resources:
- self.assertIsNotNone(resource[1].network)
- self.assertIsNotNone(resource[1].router)
- self.assertIsNotNone(resource[1].subnet)
def test_generate_resources_admin(self):
cfg.CONF.set_default('swift', False, group='service_available')
@@ -178,21 +183,17 @@
resources = account_generator.generate_resources(
self.cred_provider, admin=True)
resource_types = [k for k, _ in resources]
- # Admin, no swift, expect three credentials only
- self.assertEqual(4, len(resources))
- # Ensure create_user was invoked 4 times (4 distinct users)
- self.assertEqual(4, self.user_create_fixture.mock.call_count)
- self.assertIn('primary', resource_types)
+ self.assertEqual(self.count_admin_no_swift, len(resources))
+ self.assertEqual(self.count_admin_no_swift,
+ self.user_create_fixture.mock.call_count)
+ self.assertIn(['manager'], resource_types)
+ self.assertIn(['member'], resource_types)
self.assertIn('alt', resource_types)
self.assertIn('admin', resource_types)
self.assertIn(['reader'], resource_types)
self.assertNotIn(['fake_operator'], resource_types)
self.assertNotIn(['fake_reseller'], resource_types)
self.assertNotIn(['fake_owner'], resource_types)
- for resource in resources:
- self.assertIsNotNone(resource[1].network)
- self.assertIsNotNone(resource[1].router)
- self.assertIsNotNone(resource[1].subnet)
def test_generate_resources_swift_admin(self):
cfg.CONF.set_default('swift', True, group='service_available')
@@ -203,20 +204,16 @@
resources = account_generator.generate_resources(
self.cred_provider, admin=True)
resource_types = [k for k, _ in resources]
- # all options on, expect six credentials
- self.assertEqual(6, len(resources))
- # Ensure create_user was invoked 6 times (6 distinct users)
- self.assertEqual(6, self.user_create_fixture.mock.call_count)
- self.assertIn('primary', resource_types)
+ self.assertEqual(self.count_swift_admin, len(resources))
+ self.assertEqual(self.count_swift_admin,
+ self.user_create_fixture.mock.call_count)
+ self.assertIn(['manager'], resource_types)
+ self.assertIn(['member'], resource_types)
self.assertIn('alt', resource_types)
self.assertIn('admin', resource_types)
self.assertIn(['reader'], resource_types)
self.assertIn(['fake_operator'], resource_types)
self.assertIn(['fake_reseller'], resource_types)
- for resource in resources:
- self.assertIsNotNone(resource[1].network)
- self.assertIsNotNone(resource[1].router)
- self.assertIsNotNone(resource[1].subnet)
def test_generate_resources_swift_no_admin(self):
cfg.CONF.set_default('swift', True, group='service_available')
@@ -227,27 +224,27 @@
resources = account_generator.generate_resources(
self.cred_provider, admin=False)
resource_types = [k for k, _ in resources]
- # No Admin, swift, expect five credentials only
- self.assertEqual(5, len(resources))
- # Ensure create_user was invoked 5 times (5 distinct users)
- self.assertEqual(5, self.user_create_fixture.mock.call_count)
- self.assertIn('primary', resource_types)
+ self.assertEqual(self.count_swift_no_admin, len(resources))
+ self.assertEqual(self.count_swift_no_admin,
+ self.user_create_fixture.mock.call_count)
+ self.assertIn(['manager'], resource_types)
+ self.assertIn(['member'], resource_types)
self.assertIn('alt', resource_types)
self.assertNotIn('admin', resource_types)
self.assertIn(['reader'], resource_types)
self.assertIn(['fake_operator'], resource_types)
self.assertIn(['fake_reseller'], resource_types)
self.assertNotIn(['fake_owner'], resource_types)
- for resource in resources:
- self.assertIsNotNone(resource[1].network)
- self.assertIsNotNone(resource[1].router)
- self.assertIsNotNone(resource[1].subnet)
class TestGenerateResourcesV3(TestGenerateResourcesV2):
identity_version = 3
cred_client = 'tempest.lib.common.cred_client.V3CredsClient'
+ count_no_admin_no_swift = 6
+ count_admin_no_swift = 8
+ count_swift_admin = 10
+ count_swift_no_admin = 8
def setUp(self):
self.mock_domains()
@@ -261,6 +258,9 @@
dynamic_creds = ('tempest.lib.common.dynamic_creds.'
'DynamicCredentialProvider')
domain_is_in = False
+ expected_accounts = 7
+ expected_roles = 5
+ expected_accounts_with_network = 7
def setUp(self):
super(TestDumpAccountsV2, self).setUp()
@@ -272,6 +272,24 @@
self.resources = account_generator.generate_resources(
self.cred_provider, admin=True)
+ def _assert_accounts(self, accounts, handle, yaml_dump_mock):
+ _, f = yaml_dump_mock.call_args[0]
+ self.assertEqual(handle, f)
+ self.assertEqual(self.expected_accounts, len(accounts))
+ if self.domain_is_in:
+ self.assertIn('domain_name', accounts[0].keys())
+ else:
+ self.assertNotIn('domain_name', accounts[0].keys())
+ self.assertEqual(1, len([x for x in accounts if
+ x.get('types') == ['admin']]))
+ self.assertEqual(self.expected_roles,
+ len([x for x in accounts if 'roles' in x]))
+ accounts_with_resources = [x for x in accounts if 'resources' in x]
+ self.assertEqual(self.expected_accounts_with_network,
+ len(accounts_with_resources))
+ for account in accounts_with_resources:
+ self.assertIn('network', account.get('resources'))
+
def test_dump_accounts(self):
self.useFixture(fixtures.MockPatch('os.path.exists',
return_value=False))
@@ -285,20 +303,8 @@
self.opts.accounts)
mocked_open.assert_called_once_with(self.opts.accounts, 'w')
handle = mocked_open()
- # Ordered args in [0], keyword args in [1]
- accounts, f = yaml_dump_mock.call_args[0]
- self.assertEqual(handle, f)
- self.assertEqual(6, len(accounts))
- if self.domain_is_in:
- self.assertIn('domain_name', accounts[0].keys())
- else:
- self.assertNotIn('domain_name', accounts[0].keys())
- self.assertEqual(1, len([x for x in accounts if
- x.get('types') == ['admin']]))
- self.assertEqual(3, len([x for x in accounts if 'roles' in x]))
- for account in accounts:
- self.assertIn('resources', account)
- self.assertIn('network', account.get('resources'))
+ accounts, _ = yaml_dump_mock.call_args[0]
+ self._assert_accounts(accounts, handle, yaml_dump_mock)
def test_dump_accounts_existing_file(self):
self.useFixture(fixtures.MockPatch('os.path.exists',
@@ -316,20 +322,8 @@
rename_mock.assert_called_once_with(self.opts.accounts, backup_file)
mocked_open.assert_called_once_with(self.opts.accounts, 'w')
handle = mocked_open()
- # Ordered args in [0], keyword args in [1]
- accounts, f = yaml_dump_mock.call_args[0]
- self.assertEqual(handle, f)
- self.assertEqual(6, len(accounts))
- if self.domain_is_in:
- self.assertIn('domain_name', accounts[0].keys())
- else:
- self.assertNotIn('domain_name', accounts[0].keys())
- self.assertEqual(1, len([x for x in accounts if
- x.get('types') == ['admin']]))
- self.assertEqual(3, len([x for x in accounts if 'roles' in x]))
- for account in accounts:
- self.assertIn('resources', account)
- self.assertIn('network', account.get('resources'))
+ accounts, _ = yaml_dump_mock.call_args[0]
+ self._assert_accounts(accounts, handle, yaml_dump_mock)
class TestDumpAccountsV3(TestDumpAccountsV2):
@@ -337,6 +331,9 @@
identity_version = 3
cred_client = 'tempest.lib.common.cred_client.V3CredsClient'
domain_is_in = True
+ expected_accounts = 10
+ expected_roles = 8
+ expected_accounts_with_network = 7
def setUp(self):
self.mock_domains()
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index c5eaf7e..d3b6f88 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -33,6 +33,9 @@
from tempest.tests.lib import fake_identity
from tempest.tests.lib.services import registry_fixture
+# Store the real os.path.isfile before any test mocking replaces it
+original_isfile = os.path.isfile
+
class TestPreProvisionedCredentials(base.TestCase):
@@ -53,9 +56,9 @@
def _fake_accounts(cls, admin_role):
return [
{'username': 'test_user1', 'tenant_name': 'test_tenant1',
- 'password': 'p'},
+ 'password': 'p', 'roles': ['member']},
{'username': 'test_user2', 'project_name': 'test_tenant2',
- 'password': 'p'},
+ 'password': 'p', 'roles': ['member']},
{'username': 'test_user3', 'tenant_name': 'test_tenant3',
'password': 'p'},
{'username': 'test_user4', 'project_name': 'test_tenant4',
@@ -457,6 +460,123 @@
# Get one more
test_accounts_class.get_project_manager_creds()
+ def test_get_project_member_reader_creds_in_same_project(self):
+ # Verify that project_member and project_reader creds are allocated
+ # from the same project when both are available in the accounts file.
+ test_accounts = [
+ {'username': 'test_project_member1',
+ 'project_name': 'test_project_shared1',
+ 'password': 'p', 'roles': ['member']},
+ {'username': 'test_project_member3',
+ 'project_name': 'test_project_shared3',
+ 'password': 'p', 'roles': ['member']},
+ {'username': 'test_project_reader1',
+ 'project_name': 'test_project_shared1',
+ 'password': 'p', 'roles': ['reader']},
+ ]
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
+ return_value=test_accounts))
+ # Use a real temp dir and real os.path.isfile so the hash-file locking
+ # mechanism works correctly for multiple credential allocations.
+ tmp_dir = self.useFixture(fixtures.TempDir())
+ self.patchobject(os.path, 'isfile', original_isfile)
+ params = dict(self.fixed_params)
+ params['accounts_lock_dir'] = tmp_dir.path
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **params)
+ member_creds = test_accounts_class.get_project_member_creds()
+ reader_creds = test_accounts_class.get_project_reader_creds()
+ self.assertEqual(member_creds.project_name, reader_creds.project_name)
+
+ def test_get_match_hash_list_select_project_with_more_roles(self):
+ test_accounts = [
+ {'username': 'test_member_proj_1',
+ 'project_name': 'project_1',
+ 'password': 'p', 'roles': ['member']},
+ {'username': 'test_member_proj_2',
+ 'project_name': 'project_2',
+ 'password': 'p', 'roles': ['member']},
+ {'username': 'test_reader_proj_2',
+ 'project_name': 'project_2',
+ 'password': 'p', 'roles': ['reader']},
+ ]
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
+ return_value=test_accounts))
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ hash_list = self._get_hash_list(test_accounts)
+ result = test_accounts_class._get_match_hash_list(
+ roles=['member'], scope='project')
+ self.assertEqual(2, len(result))
+ # First hash in selected one is from project_2 which has the member
+ # as well as reader role.
+ self.assertEqual(hash_list[1], result[0])
+ self.assertEqual(hash_list[0], result[1])
+
+ def test_get_match_hash_list_with_three_roles_in_project_account(self):
+ test_accounts = [
+ {'username': 'test_manager_proj_1',
+ 'project_name': 'project_1',
+ 'password': 'p', 'roles': ['manager']},
+ {'username': 'test_manager_proj_2',
+ 'project_name': 'project_2',
+ 'password': 'p', 'roles': ['manager']},
+ {'username': 'test_member_proj_2',
+ 'project_name': 'project_2',
+ 'password': 'p', 'roles': ['member']},
+ {'username': 'test_manager_proj_3',
+ 'project_name': 'project_3',
+ 'password': 'p', 'roles': ['manager']},
+ {'username': 'test_member_proj_3',
+ 'project_name': 'project_3',
+ 'password': 'p', 'roles': ['member']},
+ {'username': 'test_reader_proj_3',
+ 'project_name': 'project_3',
+ 'password': 'p', 'roles': ['reader']},
+ ]
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
+ return_value=test_accounts))
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ hash_list = self._get_hash_list(test_accounts)
+ result = test_accounts_class._get_match_hash_list(
+ roles=['manager'], scope='project')
+ self.assertEqual(3, len(result))
+ # Hashes selection should be project_3 (with three roles),
+ # project_2 (with two roles), and then project_1 (with one role)
+ self.assertEqual(hash_list[3], result[0])
+ self.assertEqual(hash_list[1], result[1])
+ self.assertEqual(hash_list[0], result[2])
+
+ def test_get_match_hash_list_with_project_name(self):
+ test_accounts = [
+ {'username': 'test_member_proj_1',
+ 'project_name': 'project_1',
+ 'password': 'p', 'roles': ['member']},
+ {'username': 'test_reader_proj_1',
+ 'project_name': 'project_1',
+ 'password': 'p', 'roles': ['reader']},
+ {'username': 'test_member_proj_2',
+ 'project_name': 'project_2',
+ 'password': 'p', 'roles': ['member']},
+ {'username': 'test_reader_proj_2',
+ 'project_name': 'project_2',
+ 'password': 'p', 'roles': ['reader']},
+ ]
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
+ return_value=test_accounts))
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **self.fixed_params)
+ hash_list = self._get_hash_list(test_accounts)
+ result = test_accounts_class._get_match_hash_list(
+ roles=['reader'], scope='project', project_name='project_1')
+ self.assertEqual(1, len(result))
+ self.assertIn(hash_list[1], result)
+
class TestPreProvisionedCredentialsV3(TestPreProvisionedCredentials):
@@ -477,9 +597,9 @@
def _fake_accounts(cls, admin_role):
return [
{'username': 'test_user1', 'project_name': 'test_project1',
- 'domain_name': 'domain', 'password': 'p'},
+ 'domain_name': 'domain', 'password': 'p', 'roles': ['member']},
{'username': 'test_user2', 'project_name': 'test_project2',
- 'domain_name': 'domain', 'password': 'p'},
+ 'domain_name': 'domain', 'password': 'p', 'roles': ['member']},
{'username': 'test_user3', 'project_name': 'test_project3',
'domain_name': 'domain', 'password': 'p'},
{'username': 'test_user4', 'project_name': 'test_project4',
@@ -532,3 +652,27 @@
with testtools.ExpectedException(lib_exc.InvalidCredentials):
# Get one more
test_accounts_class.get_domain_manager_creds()
+
+ def test_domain_creds_not_constrained_by_project(self):
+ test_accounts = [
+ {'username': 'test_domain_manager1',
+ 'domain_name': 'test_domain', 'password': 'p',
+ 'roles': ['manager']},
+ {'username': 'test_domain_member1',
+ 'domain_name': 'test_domain', 'password': 'p',
+ 'roles': ['member']},
+ ]
+ self.useFixture(fixtures.MockPatch(
+ 'tempest.lib.common.preprov_creds.read_accounts_yaml',
+ return_value=test_accounts))
+ tmp_dir = self.useFixture(fixtures.TempDir())
+ self.patchobject(os.path, 'isfile', original_isfile)
+ params = dict(self.fixed_params)
+ params['accounts_lock_dir'] = tmp_dir.path
+ test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(
+ **params)
+ test_accounts_class.get_domain_manager_creds()
+ # get_domain_member_creds should pass as get_domain_manager_creds
+ # restrict the member creds to be in same project as manager creds
+ domain_member = test_accounts_class.get_domain_member_creds()
+ self.assertIn('test_domain_member', domain_member.username)
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index fb710ee..fa5b8d7 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -108,6 +108,16 @@
horizon: false
- job:
+ name: tempest-full-preprov-creds
+ parent: tempest-full-py3
+ description: |
+ This job runs the full set of tempest tests using pre-provisioned
+ credentials.
+ vars:
+ devstack_localrc:
+ TEMPEST_USE_TEST_ACCOUNTS: True
+
+- job:
name: tempest-full-centos-9-stream
parent: tempest-full-py3
nodeset: devstack-single-node-centos-9-stream
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index e5a25c2..6a77f8a 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -41,6 +41,11 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-full-2024-2:
irrelevant-files: *tempest-irrelevant-files
+ # NOTE(gmaan): cinder-tempest-lvm-multibackend-2024-2
+ # is added to cover the stable release testing with tempest
+ # plugin.
+ - cinder-tempest-lvm-multibackend-2024-2:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-multinode-full-py3:
irrelevant-files: *tempest-irrelevant-files
- tempest-tox-plugin-sanity-check:
@@ -113,10 +118,9 @@
- neutron-ovs-tempest-dvr:
voting: false
irrelevant-files: *tempest-irrelevant-files
- - tempest-full-test-account-py3:
- voting: false
+ - tempest-full-preprov-creds:
irrelevant-files: *tempest-irrelevant-files
- - ironic-tempest-bios-ipmi-direct:
+ - ironic-tempest-bios-ipmi-autodetect:
irrelevant-files: *tempest-irrelevant-files
- openstack-tox-bashate:
irrelevant-files: *tempest-irrelevant-files-2
@@ -133,6 +137,8 @@
irrelevant-files: *tempest-irrelevant-files
- tempest-full-py3:
irrelevant-files: *tempest-irrelevant-files
+ - tempest-full-preprov-creds:
+ irrelevant-files: *tempest-irrelevant-files
- tempest-extra-tests:
irrelevant-files: *tempest-irrelevant-files
- grenade:
@@ -149,7 +155,7 @@
irrelevant-files: *tempest-irrelevant-files
- nova-live-migration:
irrelevant-files: *tempest-irrelevant-files
- - ironic-tempest-bios-ipmi-direct:
+ - ironic-tempest-bios-ipmi-autodetect:
irrelevant-files: *tempest-irrelevant-files
experimental:
jobs:
diff --git a/zuul.d/stable-jobs.yaml b/zuul.d/stable-jobs.yaml
index 74c0b3f..217a1f4 100644
--- a/zuul.d/stable-jobs.yaml
+++ b/zuul.d/stable-jobs.yaml
@@ -24,6 +24,14 @@
nodeset: openstack-single-node-jammy
override-checkout: stable/2024.2
+# NOTE(gmaan): Keep moving this job to oldest supported release so that we
+# can cover the tempest plugin testing on stable releases.
+- job:
+ name: cinder-tempest-lvm-multibackend-2024-2
+ parent: cinder-tempest-lvm-multibackend
+ nodeset: openstack-single-node-jammy
+ override-checkout: stable/2024.2
+
- job:
name: tempest-full-2026-1-extra-tests
parent: tempest-extra-tests
diff --git a/zuul.d/tempest-specific.yaml b/zuul.d/tempest-specific.yaml
index 11c755e..d382bad 100644
--- a/zuul.d/tempest-specific.yaml
+++ b/zuul.d/tempest-specific.yaml
@@ -88,27 +88,8 @@
timeout: 5000
- job:
- name: tempest-full-test-account-py3
- parent: tempest-full-py3
- description: |
- This job runs the full set of tempest tests using pre-provisioned
- credentials instead of dynamic credentials and py3.
- Former names for this job were:
- - legacy-tempest-dsvm-full-test-accounts
- - legacy-tempest-dsvm-neutron-full-test-accounts
- - legacy-tempest-dsvm-identity-v3-test-accounts
- vars:
- devstack_localrc:
- TEMPEST_USE_TEST_ACCOUNTS: True
- # FIXME(gmann): Nova and Glance have enabled the new defaults and scope
- # by default in devstack and pre provisioned account code and testing
- # needs to be move to new RBAC design testing. Until we do that, let's
- # run these jobs with old defaults.
- NOVA_ENFORCE_SCOPE: false
- GLANCE_ENFORCE_SCOPE: false
-- job:
name: tempest-full-test-account-no-admin-py3
- parent: tempest-full-test-account-py3
+ parent: tempest-full-preprov-creds
description: |
This job runs the full set of tempest tests using pre-provisioned
credentials and py3 without having an admin account.