Merge "Fix AttributeError with 'SSHExecCommandFailed'"
diff --git a/releasenotes/notes/2024.2-intermediate-release-2a9f305375fcb462.yaml b/releasenotes/notes/2024.2-intermediate-release-2a9f305375fcb462.yaml
new file mode 100644
index 0000000..11d3a4f
--- /dev/null
+++ b/releasenotes/notes/2024.2-intermediate-release-2a9f305375fcb462.yaml
@@ -0,0 +1,5 @@
+---
+prelude: >
+ This is an intermediate release during the 2024.2 Dalmatian development
+ cycle to make new functionality available to plugins and other consumers.
+
diff --git a/releasenotes/notes/add-manager-creds-49acd9192110c3e3.yaml b/releasenotes/notes/add-manager-creds-49acd9192110c3e3.yaml
new file mode 100644
index 0000000..a5d7984
--- /dev/null
+++ b/releasenotes/notes/add-manager-creds-49acd9192110c3e3.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Add support for project manager and domain manager personas by adding
+ ``get_project_manager_creds`` and ``get_domain_manager_creds`` to
+ the ``DynamicCredentialProvider`` and ``PreProvisionedCredentialProvider``
+ classes of the common library.
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 0cc088a..c2f067c 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -177,8 +177,8 @@
# If we added the location directly, the image goes straight
# to active and no hashing is done
self.assertEqual('active', image['status'])
- self.assertIsNone(None, image['os_hash_algo'])
- self.assertIsNone(None, image['os_hash_value'])
+ self.assertIsNone(image['os_hash_algo'])
+ self.assertIsNone(image['os_hash_value'])
return image
@@ -201,8 +201,8 @@
# The image should still be active and still have no hashes
self.assertEqual('active', image['status'])
- self.assertIsNone(None, image['os_hash_algo'])
- self.assertIsNone(None, image['os_hash_value'])
+ self.assertIsNone(image['os_hash_algo'])
+ self.assertIsNone(image['os_hash_value'])
# The direct_url should still match the first location
if 'direct_url' in image:
diff --git a/tempest/api/image/v2/test_images_formats.py b/tempest/api/image/v2/test_images_formats.py
index 45626a6..48f1325 100644
--- a/tempest/api/image/v2/test_images_formats.py
+++ b/tempest/api/image/v2/test_images_formats.py
@@ -110,28 +110,25 @@
if not CONF.image_feature_enabled.image_conversion:
self.skipTest('Import image_conversion not enabled')
- if self.imgdef['format'] == 'iso':
- # TODO(danms): Glance does not properly handle ISO conversions
- # today and this is being fixed currently. Remove when this
- # is stable and able to be tested.
- self.skipTest('Glance ISO conversion is not testable')
-
- glance_noconvert = [
- # Glance does not support vmdk-sparse-with-footer with the
- # in-tree format_inspector
- 'vmdk-sparse-with-footer',
- ]
- # Any images glance does not support in *conversion* for some
- # reason will fail, even though the manifest marks them as usable.
- expect_fail = any(x in self.imgdef['name']
- for x in glance_noconvert)
+ # VMDK with footer was not supported by earlier service versions,
+ # so we need to tolerate it passing and failing (skip for the latter).
+ # See this for more info:
+ # https://bugs.launchpad.net/glance/+bug/2073262
+ is_broken = 'footer' in self.imgdef['name']
if (self.imgdef['format'] in CONF.image.disk_formats and
- self.imgdef['usable'] and not expect_fail):
+ self.imgdef['usable']):
# Usable images should end up in active state
image = self._test_image(self.imgdef, asimport=True)
- waiters.wait_for_image_status(self.client, image['id'],
- 'active')
+ try:
+ waiters.wait_for_image_status(self.client, image['id'],
+ 'active')
+ except lib_exc.TimeoutException:
+ if is_broken:
+ self.skipTest(
+ 'Older glance did not support vmdk-with-footer')
+ else:
+ raise
else:
# FIXME(danms): Make this better, but gpt will fail before
# the import even starts until glance has it in its API
@@ -147,6 +144,12 @@
'queued')
self.client.delete_image(image['id'])
+ if self.imgdef['format'] == 'iso':
+ # NOTE(danms): Glance has a special case to not convert ISO images
+ # because they are special and must remain as ISOs in order to be
+ # properly used for CD-based rescue and boot.
+ self.assertEqual('iso', image['disk_format'])
+
def _create_server_with_image_def(self, image_def, **overrides):
image_def = dict(image_def, **overrides)
image = self._test_image(image_def)
@@ -164,7 +167,9 @@
self.skipTest(
'Format %s not allowed by config' % self.imgdef['format'])
- # VMDK with footer is not supported by anyone yet until fixed:
+ # VMDK with footer was not supported by earlier service versions,
+ # so we need to tolerate it passing and failing (skip for the latter).
+ # See this for more info:
# https://bugs.launchpad.net/glance/+bug/2073262
is_broken = 'footer' in self.imgdef['name']
diff --git a/tempest/lib/common/cred_provider.py b/tempest/lib/common/cred_provider.py
index 2da206f..93b9586 100644
--- a/tempest/lib/common/cred_provider.py
+++ b/tempest/lib/common/cred_provider.py
@@ -76,6 +76,10 @@
return
@abc.abstractmethod
+ def get_domain_manager_creds(self):
+ return
+
+ @abc.abstractmethod
def get_domain_member_creds(self):
return
@@ -92,6 +96,10 @@
return
@abc.abstractmethod
+ def get_project_manager_creds(self):
+ return
+
+ @abc.abstractmethod
def get_project_member_creds(self):
return
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index 6814373..6c90938 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -432,7 +432,7 @@
cred_type = [cred_type]
credentials = self._create_creds(
roles=cred_type, scope=scope, project_id=project_id)
- elif credential_type in [['member'], ['reader']]:
+ elif credential_type in [['manager'], ['member'], ['reader']]:
credentials = self._create_creds(
roles=credential_type, scope=scope,
project_id=project_id)
@@ -492,6 +492,9 @@
def get_domain_admin_creds(self):
return self.get_credentials(['admin'], scope='domain')
+ def get_domain_manager_creds(self):
+ return self.get_credentials(['manager'], scope='domain')
+
def get_domain_member_creds(self):
return self.get_credentials(['member'], scope='domain')
@@ -504,6 +507,9 @@
def get_project_alt_admin_creds(self):
return self.get_credentials(['alt_admin'], scope='project')
+ def get_project_manager_creds(self):
+ return self.get_credentials(['manager'], scope='project')
+
def get_project_member_creds(self):
return self.get_credentials(['member'], scope='project')
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index 6d948cf..3ba7db1 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -353,6 +353,13 @@
self._creds['domain_admin'] = domain_admin
return domain_admin
+ def get_domain_manager_creds(self):
+ if self._creds.get('domain_manager'):
+ return self._creds.get('domain_manager')
+ domain_manager = self._get_creds(['manager'], scope='domain')
+ self._creds['domain_manager'] = domain_manager
+ return domain_manager
+
def get_domain_member_creds(self):
if self._creds.get('domain_member'):
return self._creds.get('domain_member')
@@ -378,6 +385,13 @@
# TODO(gmann): Implement alt admin hash.
return
+ def get_project_manager_creds(self):
+ if self._creds.get('project_manager'):
+ return self._creds.get('project_manager')
+ project_manager = self._get_creds(['manager'], scope='project')
+ self._creds['project_manager'] = project_manager
+ return project_manager
+
def get_project_member_creds(self):
if self._creds.get('project_member'):
return self._creds.get('project_member')
diff --git a/tempest/tests/lib/common/test_dynamic_creds.py b/tempest/tests/lib/common/test_dynamic_creds.py
index d3d01c0..4c2ea30 100644
--- a/tempest/tests/lib/common/test_dynamic_creds.py
+++ b/tempest/tests/lib/common/test_dynamic_creds.py
@@ -104,6 +104,14 @@
(200, {'tenant': {'id': id, 'name': name}}))))
return tenant_fix
+ def _mock_domain_create(self, id, name):
+ domain_fix = self.useFixture(fixtures.MockPatchObject(
+ self.domains_client.DomainsClient,
+ 'create_domain',
+ return_value=(rest_client.ResponseBody
+ (200, {'domain': {'id': id, 'name': name}}))))
+ return domain_fix
+
def _mock_list_roles(self, id, name):
roles_fix = self.useFixture(fixtures.MockPatchObject(
self.roles_client.RolesClient,
@@ -143,7 +151,8 @@
{'id': '1', 'name': 'FakeRole'},
{'id': '2', 'name': 'member'},
{'id': '3', 'name': 'reader'},
- {'id': '4', 'name': 'admin'}]}))))
+ {'id': '4', 'name': 'manager'},
+ {'id': '5', 'name': 'admin'}]}))))
return roles_fix
def _mock_list_ec2_credentials(self, user_id, tenant_id):
@@ -999,6 +1008,7 @@
roles_client = v3_roles_client
tenants_client = v3_projects_client
users_client = v3_users_client
+ domains_client = domains_client
token_client_class = token_client.V3TokenClient
fake_response = fake_identity._fake_v3_response
tenants_client_class = tenants_client.ProjectsClient
@@ -1263,3 +1273,47 @@
"member role already exists, ignoring conflict.")
creds.creds_client.assign_user_role.assert_called_once_with(
mock.ANY, mock.ANY, 'member')
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_project_manager_creds(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_roles('1234', 'manager')
+ self._mock_user_create('1234', 'fake_manager_user')
+ self._mock_tenant_create('1234', 'fake_manager_tenant')
+
+ user_mock = mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project')
+ user_mock.start()
+ self.addCleanup(user_mock.stop)
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_project') as user_mock:
+ manager_creds = creds.get_project_manager_creds()
+ user_mock.assert_has_calls([
+ mock.call('1234', '1234', '1234')])
+ self.assertEqual(manager_creds.username, 'fake_manager_user')
+ self.assertEqual(manager_creds.tenant_name, 'fake_manager_tenant')
+ # Verify IDs
+ self.assertEqual(manager_creds.tenant_id, '1234')
+ self.assertEqual(manager_creds.user_id, '1234')
+
+ @mock.patch('tempest.lib.common.rest_client.RestClient')
+ def test_domain_manager_creds(self, MockRestClient):
+ creds = dynamic_creds.DynamicCredentialProvider(**self.fixed_params)
+ self._mock_list_roles('1234', 'manager')
+ self._mock_user_create('1234', 'fake_manager_user')
+ self._mock_domain_create('1234', 'fake_manager_domain')
+
+ user_mock = mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_domain')
+ user_mock.start()
+ self.addCleanup(user_mock.stop)
+ with mock.patch.object(self.roles_client.RolesClient,
+ 'create_user_role_on_domain') as user_mock:
+ manager_creds = creds.get_domain_manager_creds()
+ user_mock.assert_has_calls([
+ mock.call('1234', '1234', '1234')])
+ self.assertEqual(manager_creds.username, 'fake_manager_user')
+ self.assertEqual(manager_creds.domain_name, 'fake_manager_domain')
+ # Verify IDs
+ self.assertEqual(manager_creds.domain_id, '1234')
+ self.assertEqual(manager_creds.user_id, '1234')
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index f2131dc..5a36f71 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -77,7 +77,13 @@
{'username': 'test_admin2', 'project_name': 'test_tenant12',
'password': 'p', 'roles': [admin_role]},
{'username': 'test_admin3', 'project_name': 'test_tenant13',
- 'password': 'p', 'types': ['admin']}]
+ 'password': 'p', 'types': ['admin']},
+ {'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):
super(TestPreProvisionedCredentials, self).setUp()
@@ -319,7 +325,7 @@
calls = get_free_hash_mock.mock.mock_calls
self.assertEqual(len(calls), 1)
args = calls[0][1][0]
- self.assertEqual(len(args), 10)
+ self.assertEqual(len(args), 12)
for i in admin_hashes:
self.assertNotIn(i, args)
@@ -431,6 +437,26 @@
# 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):
@@ -480,4 +506,29 @@
{'username': 'test_admin2', 'project_name': 'test_project12',
'domain_name': 'domain', 'password': 'p', 'roles': [admin_role]},
{'username': 'test_admin3', 'project_name': 'test_tenant13',
- 'domain_name': 'domain', 'password': 'p', 'types': ['admin']}]
+ 'domain_name': 'domain', 'password': 'p', 'types': ['admin']},
+ {'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()