Merge "Add new test case for volume retype"
diff --git a/doc/source/supported_version.rst b/doc/source/supported_version.rst
index f38b6ca..9c03e7f 100644
--- a/doc/source/supported_version.rst
+++ b/doc/source/supported_version.rst
@@ -9,6 +9,7 @@
Tempest master supports the below OpenStack Releases:
+* 2025.2
* 2025.1
* 2024.2
* 2024.1
diff --git a/releasenotes/notes/add-config-nova-policy-roles-37fc4ef511f97f50.yaml b/releasenotes/notes/add-config-nova-policy-roles-37fc4ef511f97f50.yaml
new file mode 100644
index 0000000..f357b00
--- /dev/null
+++ b/releasenotes/notes/add-config-nova-policy-roles-37fc4ef511f97f50.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ A new config option ``nova_policy_roles`` is added in the
+ ``compute-feature-enabled`` section. This can be used to
+ configure the available roles that are used as default in
+ nova policy rules.
diff --git a/releasenotes/notes/add-server-migrations-clients-ffbf5cbdf7818305.yaml b/releasenotes/notes/add-server-migrations-clients-ffbf5cbdf7818305.yaml
new file mode 100644
index 0000000..8e88513
--- /dev/null
+++ b/releasenotes/notes/add-server-migrations-clients-ffbf5cbdf7818305.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Add the server live migration list and force complete service
+ clients.
diff --git a/releasenotes/notes/tempest-2025-2-release-085c56b9b4cf2c84.yaml b/releasenotes/notes/tempest-2025-2-release-085c56b9b4cf2c84.yaml
new file mode 100644
index 0000000..cfc6b19
--- /dev/null
+++ b/releasenotes/notes/tempest-2025-2-release-085c56b9b4cf2c84.yaml
@@ -0,0 +1,17 @@
+---
+prelude: >
+ This release is to tag Tempest for OpenStack 2025.2 release.
+ This release marks the start of 2025.2 release support in Tempest.
+ After this release, Tempest will support below OpenStack Releases:
+
+ * 2025.2
+ * 2025.1
+ * 2024.2
+ * 2024.1
+
+ Current development of Tempest is for OpenStack 2026.1 development
+ cycle. Every Tempest commit is also tested against master during
+ the 2026.1 cycle. However, this does not necessarily mean that using
+ Tempest as of this tag will work against a 2026.1 (or future release)
+ cloud.
+ To be on safe side, use this tag to test the OpenStack 2025.2 release.
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 058f65f..5439ab6f 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@
:maxdepth: 1
unreleased
+ v44.0.0
v43.0.0
v42.0.0
v41.0.0
diff --git a/releasenotes/source/v44.0.0.rst b/releasenotes/source/v44.0.0.rst
new file mode 100644
index 0000000..bdb3b69
--- /dev/null
+++ b/releasenotes/source/v44.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v44.0.0 Release Notes
+=====================
+
+.. release-notes:: 44.0.0 Release Notes
+ :version: 44.0.0
diff --git a/tempest/api/compute/admin/test_assisted_volume_snapshots.py b/tempest/api/compute/admin/test_assisted_volume_snapshots.py
index b7be796..034efc9 100644
--- a/tempest/api/compute/admin/test_assisted_volume_snapshots.py
+++ b/tempest/api/compute/admin/test_assisted_volume_snapshots.py
@@ -26,11 +26,6 @@
create_default_network = True
- # TODO(gmann): Remove the admin access to service user
- # once nova change the default of this API to service
- # role. To merge the nova changing the policy default
- # we need to use token with admin as well as service
- # role and later we can use only service token.
credentials = ['primary', 'admin', ['service_user', 'admin', 'service']]
@classmethod
@@ -39,6 +34,13 @@
if not CONF.service_available.cinder:
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+ # NOTE(gmaan): If new policy is enforced and and service role
+ # is present in nova then use service user (no admin role) for
+ # assisted volume snapshots APIs.
+ if (CONF.enforce_scope.nova and 'service' in
+ CONF.compute_feature_enabled.nova_policy_roles):
+ cls.credentials = [
+ 'primary', 'admin', ['service_user', 'service']]
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index f6a1ae9..bd2e7bf 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -26,6 +26,7 @@
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exceptions
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -34,6 +35,7 @@
class LiveMigrationTestBase(base.BaseV2ComputeAdminTest):
"""Test live migration operations supported by admin user"""
+ credentials = ['primary', 'admin', 'project_manager']
create_default_network = True
@classmethod
@@ -51,13 +53,15 @@
@classmethod
def setup_clients(cls):
super(LiveMigrationTestBase, cls).setup_clients()
- cls.admin_migration_client = cls.os_admin.migrations_client
+ cls.migration_client = cls.os_admin.migrations_client
cls.networks_client = cls.os_primary.networks_client
cls.subnets_client = cls.os_primary.subnets_client
cls.ports_client = cls.os_primary.ports_client
cls.trunks_client = cls.os_primary.trunks_client
+ cls.mgr_server_client = cls.admin_servers_client
- def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
+ def _migrate_server_to(self, server_id, dest_host, volume_backed=False,
+ use_manager_client=False):
kwargs = dict()
block_migration = getattr(self, 'block_migration', None)
if self.block_migration is None:
@@ -66,7 +70,13 @@
block_migration = (CONF.compute_feature_enabled.
block_migration_for_live_migration and
not volume_backed)
- self.admin_servers_client.live_migrate_server(
+ if use_manager_client:
+ self.mgr_server_client = self.os_project_manager.servers_client
+ LOG.info("Using project manager for live migrating server: %s, "
+ "project manager user id: %s",
+ server_id, self.mgr_server_client.user_id)
+
+ self.mgr_server_client.live_migrate_server(
server_id, host=dest_host, block_migration=block_migration,
**kwargs)
@@ -74,11 +84,20 @@
volume_backed=False):
# If target_host is None, check whether source host is different with
# the new host after migration.
+ use_manager_client = False
if target_host is None:
source_host = self.get_host_for_server(server_id)
- self._migrate_server_to(server_id, target_host, volume_backed)
+ # NOTE(gmaan): If new policy is enforced and and manager role
+ # is present in nova then use manager user to live migrate.
+ if (CONF.enforce_scope.nova and 'manager' in
+ CONF.compute_feature_enabled.nova_policy_roles):
+ use_manager_client = True
+
+ self._migrate_server_to(server_id, target_host, volume_backed,
+ use_manager_client)
waiters.wait_for_server_status(self.servers_client, server_id, state)
- migration_list = (self.admin_migration_client.list_migrations()
+
+ migration_list = (self.os_admin.migrations_client.list_migrations()
['migrations'])
msg = ("Live Migration failed. Migrations list for Instance "
@@ -98,6 +117,9 @@
class LiveMigrationTest(LiveMigrationTestBase):
max_microversion = '2.24'
block_migration = None
+ # If test case want to request the destination host to Nova
+ # otherwise Nova scheduler will pick one.
+ request_host = True
@classmethod
def setup_credentials(cls):
@@ -119,11 +141,12 @@
server_id = self.create_test_server(wait_until="ACTIVE",
volume_backed=volume_backed)['id']
source_host = self.get_host_for_server(server_id)
- if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
+ if (self.request_host and
+ CONF.compute_feature_enabled.can_migrate_between_any_hosts):
+ destination_host = self.get_host_other_than(server_id)
+ else:
# not to specify a host so that the scheduler will pick one
destination_host = None
- else:
- destination_host = self.get_host_other_than(server_id)
if state == 'PAUSED':
self.admin_servers_client.pause_server(server_id)
@@ -381,3 +404,153 @@
min_microversion = '2.25'
max_microversion = 'latest'
block_migration = 'auto'
+
+
+class LiveMigrationWithoutHostTest(LiveMigrationTest):
+ # Test live migrations without host and let Nova scheduler will pick one.
+ request_host = False
+
+ @classmethod
+ def skip_checks(cls):
+ super(LiveMigrationWithoutHostTest, cls).skip_checks()
+ if not CONF.compute_feature_enabled.can_migrate_between_any_hosts:
+ skip_msg = ("Existing live migration tests are configured to live "
+ "migrate without requesting host.")
+ raise cls.skipException(skip_msg)
+
+
+class LiveMigrationManagerWorkflowTest(LiveMigrationTestBase):
+ # This test shows live migrations workflow for the project manager.
+ # NOTE(gmaan): microversion 2.80 adds project_id query param in list
+ # migration API which is what needed by project manager to request
+ # their project migration list.
+ min_microversion = '2.80'
+ block_migration = None
+ request_host = False
+
+ @classmethod
+ def skip_checks(cls):
+ super(LiveMigrationManagerWorkflowTest, cls).skip_checks()
+ # If RBAC new defaults (manager defaults) are not present then we have
+ # existing test cases tests the live migration scenarios.
+ if (not CONF.enforce_scope.nova or 'manager' not in
+ CONF.compute_feature_enabled.nova_policy_roles):
+ skip_msg = ("Nova RBAC new defaults are not enabled or manager "
+ "role is not present so skipping the project manager "
+ "specific test.")
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def setup_clients(cls):
+ super(LiveMigrationManagerWorkflowTest, cls).setup_clients()
+ cls.mgr_server_client = cls.os_project_manager.servers_client
+ LOG.info("Using project manager for live migrating servers, "
+ "project manager user id: %s",
+ cls.mgr_server_client.user_id)
+
+ def _initiate_live_migration(self):
+ # Project member create the server.
+ server_id = self.create_test_server(wait_until="ACTIVE")['id']
+ source_host = self.get_host_for_server(server_id)
+ # Project manager does not know about host info so they will not
+ # specify a host to nova and let nova scheduler to pick one.
+ dest_host = None
+
+ LOG.info("Live migrate from source %s", source_host)
+ # Live migrate an instance to another host
+ self._migrate_server_to(
+ server_id, dest_host, use_manager_client=True)
+ # Try to list the in-progress live migation. This list can be empty
+ # if migration is already completed.
+ in_progress_migrations = (
+ self.mgr_server_client.list_in_progress_live_migration(
+ server_id)['migrations'])
+ LOG.info("in-progress live migrations: %s", in_progress_migrations)
+
+ in_progress_migration_uuid = None
+ for migration in in_progress_migrations:
+ if (migration['server_uuid'] == server_id):
+ in_progress_migration_uuid = migration['uuid']
+ # Project manager should not get any host related field.
+ self.assertIsNone(migration['dest_compute'])
+ self.assertIsNone(migration['dest_host'])
+ self.assertIsNone(migration['dest_node'])
+ self.assertIsNone(migration['source_compute'])
+ self.assertIsNone(migration['source_node'])
+
+ return server_id, source_host, in_progress_migration_uuid
+
+ @decorators.attr(type='multinode')
+ @decorators.idempotent_id('cc4e2431-4476-49b0-9a80-d7a2f638f091')
+ def test_live_migration_by_project_manager(self):
+ """Tests live migrations workflow for the project manager.
+
+ - Create server by project member.
+ - Project manager perform the below steps:
+
+ * Initiate the live migration.
+ * List the in-progress live migration. If migration is completed
+ then list might be empty but test does not fail for empty list.
+ * Assuming migration is in progress, force complete the live
+ migration. Test do not fail if migration is already completed
+ and force complete request raise error.
+ * Wait for server to be active.
+ * List migrations with project_id filter, means request only
+ their project migrations. This should return the migration
+ initiated by project manager.
+ * Check if server is migrated to the differnet host than source
+ host.
+ """
+ server_id, source_host, in_progress_migration_uuid = (
+ self._initiate_live_migration())
+ try:
+ # If we know that migration is in progress then try to force
+ # complete it but there is chance that migration might be
+ # completed before test send request to nova. In that case,
+ # test will not fail. It will skip the force complete and
+ # resume to the next step.
+ if in_progress_migration_uuid:
+ LOG.info("Starting force complete live migrations: %s",
+ in_progress_migration_uuid)
+ self.mgr_server_client.force_complete_live_migration(
+ server_id, in_progress_migration_uuid)
+ LOG.info("Finish force complete live migrations: %s",
+ in_progress_migration_uuid)
+ except lib_exceptions.BadRequest:
+ # If migration is already completed then nova will raise
+ # HTTPBadRequest. In that case, log the info and execute the
+ # rest of the steps.
+ LOG.info("Server %s live migration %s is already completed. Due "
+ "to that force completed live migration is not "
+ "performed.", server_id, in_progress_migration_uuid)
+
+ waiters.wait_for_server_status(self.mgr_server_client,
+ server_id, 'ACTIVE')
+ # List migration with project_id as filter so that manager can
+ # get its own project migrations.
+ mgr_migration_client = self.os_project_manager.migrations_client
+ project_id = mgr_migration_client.project_id
+ migrations = (mgr_migration_client.list_migrations(
+ migration_type='live-migration',
+ project_id=project_id)['migrations'])
+ migration_uuid = None
+ LOG.info("Project %s migrations list: %s", project_id, migrations)
+ for migration in migrations:
+ if (migration['instance_uuid'] == server_id):
+ migration_uuid = migration['uuid']
+ # Check if project manager get other project migrations
+ self.assertEqual(project_id, migration['project_id'])
+ # Project manager should not get any host related field.
+ self.assertIsNone(migration['dest_compute'])
+ self.assertIsNone(migration['dest_host'])
+ self.assertIsNone(migration['dest_node'])
+ self.assertIsNone(migration['source_compute'])
+ self.assertIsNone(migration['source_node'])
+ self.assertIsNotNone(migration_uuid)
+ if in_progress_migration_uuid:
+ self.assertEqual(in_progress_migration_uuid, migration_uuid)
+ msg = ("Server %s Live Migration %s failed.",
+ server_id, migration_uuid)
+ # Check if server is migrated to the differnet host than source host.
+ self.assertNotEqual(source_host,
+ self.get_host_for_server(server_id), msg)
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index fa8a737..88847e6 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_log import log as logging
import testtools
from tempest.api.compute import base
@@ -22,15 +23,27 @@
from tempest.lib import exceptions
CONF = config.CONF
+LOG = logging.getLogger(__name__)
class MigrationsAdminTest(base.BaseV2ComputeAdminTest):
"""Test migration operations supported by admin user"""
+ credentials = ['primary', 'admin', 'project_manager']
+
@classmethod
def setup_clients(cls):
super(MigrationsAdminTest, cls).setup_clients()
cls.client = cls.os_admin.migrations_client
+ cls.mgr_server_client = cls.admin_servers_client
+ # NOTE(gmaan): If new policy is enforced and and manager role
+ # is present in nova then use manager user to live migrate.
+ if (CONF.enforce_scope.nova and 'manager' in
+ CONF.compute_feature_enabled.nova_policy_roles):
+ cls.mgr_server_client = cls.os_project_manager.servers_client
+ LOG.info("Using project manager for migrating servers, "
+ "project manager user id: %s",
+ cls.mgr_server_client.user_id)
@decorators.idempotent_id('75c0b83d-72a0-4cf8-a153-631e83e7d53f')
def test_list_migrations(self):
@@ -143,7 +156,7 @@
server = self.create_test_server(wait_until="ACTIVE")
src_host = self.get_host_for_server(server['id'])
- self.admin_servers_client.migrate_server(server['id'])
+ self.mgr_server_client.migrate_server(server['id'])
waiters.wait_for_server_status(self.servers_client,
server['id'], 'VERIFY_RESIZE')
diff --git a/tempest/api/compute/admin/test_server_external_events.py b/tempest/api/compute/admin/test_server_external_events.py
index d867a39..6b26c68 100644
--- a/tempest/api/compute/admin/test_server_external_events.py
+++ b/tempest/api/compute/admin/test_server_external_events.py
@@ -19,11 +19,6 @@
class ServerExternalEventsTest(base.BaseV2ComputeAdminTest):
"""Test server external events test"""
- # TODO(gmann): Remove the admin access to service user
- # once nova change the default of this API to service
- # role. To merge the nova changing the policy default
- # we need to use token with admin as well as service
- # role and later we can use only service token.
credentials = ['primary', 'admin', ['service_user', 'admin', 'service']]
@decorators.idempotent_id('6bbf4723-61d2-4372-af55-7ba27f1c9ba6')
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index 9576b74..423407e 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -24,6 +24,8 @@
class TestVolumeSwapBase(base.BaseV2ComputeAdminTest):
create_default_network = True
+ credentials = ['primary', 'admin', ['service_user', 'admin', 'service']]
+
@classmethod
def setup_credentials(cls):
cls.prepare_instance_network()
@@ -37,6 +39,11 @@
if not CONF.compute_feature_enabled.swap_volume:
raise cls.skipException("Swapping volumes is not supported.")
+ @classmethod
+ def setup_clients(cls):
+ super(TestVolumeSwapBase, cls).setup_clients()
+ cls.service_client = cls.os_service_user.servers_client
+
def wait_for_server_volume_swap(self, server_id, old_volume_id,
new_volume_id):
"""Waits for a server to swap the old volume to a new one."""
@@ -74,16 +81,17 @@
class TestVolumeSwap(TestVolumeSwapBase):
- """The test suite for swapping of volume with admin user"""
+ """The test suite for swapping of volume with service user"""
# NOTE(mriedem): This is an uncommon scenario to call the compute API
# to swap volumes directly; swap volume is primarily only for volume
# live migration and retype callbacks from the volume service, and is slow
# so it's marked as such.
+ @decorators.skip_because(bug='2112187')
@decorators.attr(type='slow')
@decorators.idempotent_id('1769f00d-a693-4d67-a631-6a3496773813')
def test_volume_swap(self):
- """Test swapping of volume attached to server with admin user
+ """Test swapping of volume attached to server with service user
The following is the scenario outline:
@@ -91,11 +99,8 @@
2. Create a volume "volume2" with non-admin.
3. Boot an instance "instance1" with non-admin.
4. Attach "volume1" to "instance1" with non-admin.
- 5. Swap volume from "volume1" to "volume2" as admin.
- 6. Check the swap volume is successful and "volume2"
- is attached to "instance1" and "volume1" is in available state.
- 7. Swap volume from "volume2" to "volume1" as admin.
- 8. Check the swap volume is successful and "volume1"
+ 5. Swap volume from "volume1" to "volume2" as service.
+ 6. Check the swap volume is rejected and "volume1"
is attached to "instance1" and "volume2" is in available state.
"""
# Create two volumes.
@@ -118,34 +123,19 @@
# Attach "volume1" to server
self.attach_volume(server, volume1)
# Swap volume from "volume1" to "volume2"
- self.admin_servers_client.update_attached_volume(
+ self.assertRaises(
+ lib_exc.Conflict, self.service_client.update_attached_volume,
server['id'], volume1['id'], volumeId=volume2['id'])
- waiters.wait_for_volume_resource_status(self.volumes_client,
- volume1['id'], 'available')
- waiters.wait_for_volume_resource_status(self.volumes_client,
- volume2['id'], 'in-use')
- self.wait_for_server_volume_swap(server['id'], volume1['id'],
- volume2['id'])
- # Verify "volume2" is attached to the server
- vol_attachments = self.servers_client.list_volume_attachments(
- server['id'])['volumeAttachments']
- self.assertEqual(1, len(vol_attachments))
- self.assertIn(volume2['id'], vol_attachments[0]['volumeId'])
-
- # Swap volume from "volume2" to "volume1"
- self.admin_servers_client.update_attached_volume(
- server['id'], volume2['id'], volumeId=volume1['id'])
- waiters.wait_for_volume_resource_status(self.volumes_client,
- volume2['id'], 'available')
- waiters.wait_for_volume_resource_status(self.volumes_client,
- volume1['id'], 'in-use')
- self.wait_for_server_volume_swap(server['id'], volume2['id'],
- volume1['id'])
# Verify "volume1" is attached to the server
vol_attachments = self.servers_client.list_volume_attachments(
server['id'])['volumeAttachments']
self.assertEqual(1, len(vol_attachments))
self.assertIn(volume1['id'], vol_attachments[0]['volumeId'])
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, volume1['id'], 'in-use')
+ # verify "volume2" is still available
+ waiters.wait_for_volume_resource_status(
+ self.volumes_client, volume2['id'], 'available')
class TestMultiAttachVolumeSwap(TestVolumeSwapBase):
@@ -194,9 +184,8 @@
4. Attach "volume1" to "server1" with non-admin.
5. Attach "volume1" to "server2" with non-admin.
6. Swap "volume1" to "volume2" on "server1"
- 7. Check "volume1" is attached to "server2" and not attached to
- "server1"
- 8. Check "volume2" is attached to "server1".
+ 7. Check the swap volume is rejected and "volume1" is attached to
+ "server1".
"""
multiattach_vol_type = CONF.volume.volume_type_multiattach
# Create two volumes.
@@ -237,23 +226,22 @@
server2 = servers[1]
self.attach_volume(server2, volume1)
- # Swap volume1 to volume2 on server1, volume1 should remain attached
- # to server 2
- self.admin_servers_client.update_attached_volume(
+ # Swap volume is rejected by nova
+ self.assertRaises(
+ lib_exc.Conflict, self.service_client.update_attached_volume,
server1['id'], volume1['id'], volumeId=volume2['id'])
- # volume1 will return to in-use after the swap
+
+ # volume1 remains in in-use and volume2 in available
waiters.wait_for_volume_resource_status(self.volumes_client,
volume1['id'], 'in-use')
waiters.wait_for_volume_resource_status(self.volumes_client,
- volume2['id'], 'in-use')
- self.wait_for_server_volume_swap(server1['id'], volume1['id'],
- volume2['id'])
+ volume2['id'], 'available')
- # Verify volume2 is attached to server1
+ # Verify volume1 is attached to server1
vol_attachments = self.servers_client.list_volume_attachments(
server1['id'])['volumeAttachments']
self.assertEqual(1, len(vol_attachments))
- self.assertIn(volume2['id'], vol_attachments[0]['volumeId'])
+ self.assertIn(volume1['id'], vol_attachments[0]['volumeId'])
# Verify volume1 is still attached to server2
vol_attachments = self.servers_client.list_volume_attachments(
diff --git a/tempest/api/compute/admin/test_volumes_negative.py b/tempest/api/compute/admin/test_volumes_negative.py
index 55c842f..3d42388 100644
--- a/tempest/api/compute/admin/test_volumes_negative.py
+++ b/tempest/api/compute/admin/test_volumes_negative.py
@@ -26,6 +26,7 @@
"""Negative tests of volume swapping"""
create_default_network = True
+ credentials = ['primary', 'admin', ['service_user', 'admin', 'service']]
@classmethod
def setup_credentials(cls):
@@ -39,6 +40,11 @@
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+ @classmethod
+ def setup_clients(cls):
+ super(VolumesAdminNegativeTest, cls).setup_clients()
+ cls.service_client = cls.os_service_user.servers_client
+
@decorators.attr(type=['negative'])
@decorators.idempotent_id('309b5ecd-0585-4a7e-a36f-d2b2bf55259d')
def test_update_attached_volume_with_nonexistent_volume_in_uri(self):
@@ -47,10 +53,11 @@
volume = self.create_volume()
nonexistent_volume = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound,
- self.admin_servers_client.update_attached_volume,
+ self.service_client.update_attached_volume,
self.server['id'], nonexistent_volume,
volumeId=volume['id'])
+ @decorators.skip_because(bug='2112187')
@decorators.related_bug('1629110', status_code=400)
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7dcac15a-b107-46d3-a5f6-cb863f4e454a')
@@ -71,8 +78,8 @@
self.attach_volume(self.server, volume)
nonexistent_volume = data_utils.rand_uuid()
- self.assertRaises(lib_exc.BadRequest,
- self.admin_servers_client.update_attached_volume,
+ self.assertRaises(lib_exc.Conflict,
+ self.service_servers_client.update_attached_volume,
self.server['id'], volume['id'],
volumeId=nonexistent_volume)
@@ -89,6 +96,7 @@
volume_min_microversion = '3.27'
create_default_network = True
+ credentials = ['primary', 'admin', ['service_user', 'admin', 'service']]
@classmethod
def setup_credentials(cls):
@@ -101,9 +109,15 @@
if not CONF.compute_feature_enabled.volume_multiattach:
raise cls.skipException('Volume multi-attach is not available.')
+ @classmethod
+ def setup_clients(cls):
+ super(UpdateMultiattachVolumeNegativeTest, cls).setup_clients()
+ cls.service_client = cls.os_service_user.servers_client
+
@decorators.attr(type=['negative'])
@decorators.idempotent_id('7576d497-b7c6-44bd-9cc5-c5b4e50fec71')
@utils.services('volume')
+ @decorators.skip_because(bug='2112187')
def test_multiattach_rw_volume_update_failure(self):
"""Test swapping volume attached to multi-servers with read-write mode
@@ -158,10 +172,10 @@
# Assert that a BadRequest is raised when we attempt to update volume1
# to volume2 on server1 or server2.
self.assertRaises(lib_exc.BadRequest,
- self.admin_servers_client.update_attached_volume,
+ self.service_client.update_attached_volume,
server1['id'], vol1['id'], volumeId=vol2['id'])
self.assertRaises(lib_exc.BadRequest,
- self.admin_servers_client.update_attached_volume,
+ self.service_client.update_attached_volume,
server2['id'], vol1['id'], volumeId=vol2['id'])
# Fetch the volume 1 to check the current attachments.
diff --git a/tempest/config.py b/tempest/config.py
index bf906b4..fec7692 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -627,6 +627,14 @@
cfg.BoolOpt('unified_limits',
default=False,
help='Does the test environment support unified limits?'),
+ cfg.ListOpt('nova_policy_roles',
+ default=['admin', 'member', 'reader'],
+ help='Compute service API policies roles list. List all the '
+ 'roles which are used as default in Nova policy rules. '
+ 'This config option value is used to run the tests with '
+ 'the available roles in Nova. For example, if manager '
+ 'role is not present in the nova release then tempest '
+ 'will use old defaults role token to call nova APIs'),
]
diff --git a/tempest/lib/api_schema/response/compute/v2_1/servers.py b/tempest/lib/api_schema/response/compute/v2_1/servers.py
index 14e2d3b..d86ac6a 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/servers.py
@@ -520,3 +520,49 @@
'type': 'object'
}
}
+
+list_live_migrations = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'migrations': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'integer'},
+ 'status': {'type': ['string', 'null']},
+ 'server_uuid': {'type': ['string', 'null']},
+ 'source_node': {'type': ['string', 'null']},
+ 'source_compute': {'type': ['string', 'null']},
+ 'dest_node': {'type': ['string', 'null']},
+ 'dest_compute': {'type': ['string', 'null']},
+ 'dest_host': {'type': ['string', 'null']},
+ 'disk_processed_bytes': {'type': ['integer', 'null']},
+ 'disk_remaining_bytes': {'type': ['integer', 'null']},
+ 'disk_total_bytes': {'type': ['integer', 'null']},
+ 'memory_processed_bytes': {
+ 'type': ['integer', 'null']},
+ 'memory_remaining_bytes': {
+ 'type': ['integer', 'null']},
+ 'memory_total_bytes': {'type': ['integer', 'null']},
+ 'created_at': parameter_types.date_time,
+ 'updated_at': parameter_types.date_time_or_null
+ },
+ 'additionalProperties': False,
+ 'required': [
+ 'id', 'status', 'instance_uuid', 'source_node',
+ 'source_compute', 'dest_node', 'dest_compute',
+ 'dest_host', 'disk_processed_bytes',
+ 'disk_remaining_bytes', 'disk_total_bytes',
+ 'memory_processed_bytes', 'memory_remaining_bytes',
+ 'memory_total_bytes', 'created_at', 'updated_at'
+ ]
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['migrations']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_100/servers.py b/tempest/lib/api_schema/response/compute/v2_100/servers.py
index 8721387..8a2c15d 100644
--- a/tempest/lib/api_schema/response/compute/v2_100/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_100/servers.py
@@ -127,3 +127,4 @@
get_remote_consoles = copy.deepcopy(servers299.get_remote_consoles)
show_instance_action = copy.deepcopy(servers299.show_instance_action)
create_backup = copy.deepcopy(servers299.create_backup)
+list_live_migrations = copy.deepcopy(servers299.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_16/servers.py b/tempest/lib/api_schema/response/compute/v2_16/servers.py
index 2b3ce38..f09ea7f 100644
--- a/tempest/lib/api_schema/response/compute/v2_16/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_16/servers.py
@@ -173,3 +173,4 @@
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
create_backup = copy.deepcopy(servers.create_backup)
+list_live_migrations = copy.deepcopy(servers.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_19/servers.py b/tempest/lib/api_schema/response/compute/v2_19/servers.py
index ba3d787..5cb5bf3 100644
--- a/tempest/lib/api_schema/response/compute/v2_19/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -63,3 +63,4 @@
list_volume_attachments = copy.deepcopy(serversv216.list_volume_attachments)
show_instance_action = copy.deepcopy(serversv216.show_instance_action)
create_backup = copy.deepcopy(serversv216.create_backup)
+list_live_migrations = copy.deepcopy(serversv216.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_26/servers.py b/tempest/lib/api_schema/response/compute/v2_26/servers.py
index 123eb72..4ce7f90 100644
--- a/tempest/lib/api_schema/response/compute/v2_26/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -106,3 +106,4 @@
list_volume_attachments = copy.deepcopy(servers219.list_volume_attachments)
show_instance_action = copy.deepcopy(servers219.show_instance_action)
create_backup = copy.deepcopy(servers219.create_backup)
+list_live_migrations = copy.deepcopy(servers219.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_3/servers.py b/tempest/lib/api_schema/response/compute/v2_3/servers.py
index d19f1ad..c7e0147 100644
--- a/tempest/lib/api_schema/response/compute/v2_3/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_3/servers.py
@@ -178,3 +178,4 @@
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
create_backup = copy.deepcopy(servers.create_backup)
+list_live_migrations = copy.deepcopy(servers.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_45/servers.py b/tempest/lib/api_schema/response/compute/v2_45/servers.py
index cb0fc13..0746465 100644
--- a/tempest/lib/api_schema/response/compute/v2_45/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_45/servers.py
@@ -47,3 +47,4 @@
attach_volume = copy.deepcopy(servers226.attach_volume)
show_volume_attachment = copy.deepcopy(servers226.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers226.list_volume_attachments)
+list_live_migrations = copy.deepcopy(servers226.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
index 1399c2d..d24cc25 100644
--- a/tempest/lib/api_schema/response/compute/v2_47/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -72,3 +72,4 @@
list_volume_attachments = copy.deepcopy(servers245.list_volume_attachments)
show_instance_action = copy.deepcopy(servers226.show_instance_action)
create_backup = copy.deepcopy(servers245.create_backup)
+list_live_migrations = copy.deepcopy(servers245.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_48/servers.py b/tempest/lib/api_schema/response/compute/v2_48/servers.py
index 5b53906..a500155 100644
--- a/tempest/lib/api_schema/response/compute/v2_48/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_48/servers.py
@@ -134,3 +134,4 @@
list_volume_attachments = copy.deepcopy(servers247.list_volume_attachments)
show_instance_action = copy.deepcopy(servers247.show_instance_action)
create_backup = copy.deepcopy(servers247.create_backup)
+list_live_migrations = copy.deepcopy(servers247.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_51/servers.py b/tempest/lib/api_schema/response/compute/v2_51/servers.py
index 50d6aaa..27e5f45 100644
--- a/tempest/lib/api_schema/response/compute/v2_51/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_51/servers.py
@@ -41,3 +41,4 @@
show_volume_attachment = copy.deepcopy(servers248.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers248.list_volume_attachments)
create_backup = copy.deepcopy(servers248.create_backup)
+list_live_migrations = copy.deepcopy(servers248.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_54/servers.py b/tempest/lib/api_schema/response/compute/v2_54/servers.py
index 9de3016..bef1e7f 100644
--- a/tempest/lib/api_schema/response/compute/v2_54/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_54/servers.py
@@ -60,3 +60,4 @@
list_volume_attachments = copy.deepcopy(servers251.list_volume_attachments)
show_instance_action = copy.deepcopy(servers251.show_instance_action)
create_backup = copy.deepcopy(servers251.create_backup)
+list_live_migrations = copy.deepcopy(servers251.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_57/servers.py b/tempest/lib/api_schema/response/compute/v2_57/servers.py
index ee91391..7bee542 100644
--- a/tempest/lib/api_schema/response/compute/v2_57/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_57/servers.py
@@ -64,3 +64,4 @@
list_volume_attachments = copy.deepcopy(servers254.list_volume_attachments)
show_instance_action = copy.deepcopy(servers254.show_instance_action)
create_backup = copy.deepcopy(servers254.create_backup)
+list_live_migrations = copy.deepcopy(servers254.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_58/servers.py b/tempest/lib/api_schema/response/compute/v2_58/servers.py
index 637b765..3e7be49 100644
--- a/tempest/lib/api_schema/response/compute/v2_58/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_58/servers.py
@@ -43,3 +43,4 @@
show_volume_attachment = copy.deepcopy(servers257.show_volume_attachment)
list_volume_attachments = copy.deepcopy(servers257.list_volume_attachments)
create_backup = copy.deepcopy(servers257.create_backup)
+list_live_migrations = copy.deepcopy(servers257.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_59/servers.py b/tempest/lib/api_schema/response/compute/v2_59/servers.py
new file mode 100644
index 0000000..a52c3f4
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_59/servers.py
@@ -0,0 +1,57 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_58 import servers as servers258
+
+###########################################################################
+#
+# 2.59:
+#
+# The uuid value is now returned in the response body in addition to the
+# migration id for the following API responses:
+#
+# - GET /os-migrations
+# - GET /servers/{server_id}/migrations/{migration_id}
+# - GET /servers/{server_id}/migrations
+#
+###########################################################################
+
+list_live_migrations = copy.deepcopy(servers258.list_live_migrations)
+list_live_migrations['response_body']['properties']['migrations']['items'][
+ 'properties'].update({'uuid': {'type': 'string', 'format': 'uuid'}})
+list_live_migrations['response_body']['properties']['migrations']['items'][
+ 'required'].append('uuid')
+
+# Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+list_servers = copy.deepcopy(servers258.list_servers)
+show_server_diagnostics = copy.deepcopy(servers258.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers258.get_remote_consoles)
+list_tags = copy.deepcopy(servers258.list_tags)
+update_all_tags = copy.deepcopy(servers258.update_all_tags)
+delete_all_tags = copy.deepcopy(servers258.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers258.check_tag_existence)
+update_tag = copy.deepcopy(servers258.update_tag)
+delete_tag = copy.deepcopy(servers258.delete_tag)
+get_server = copy.deepcopy(servers258.get_server)
+list_servers_detail = copy.deepcopy(servers258.list_servers_detail)
+update_server = copy.deepcopy(servers258.update_server)
+rebuild_server = copy.deepcopy(servers258.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+ servers258.rebuild_server_with_admin_pass)
+attach_volume = copy.deepcopy(servers258.attach_volume)
+show_volume_attachment = copy.deepcopy(servers258.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers258.list_volume_attachments)
+show_instance_action = copy.deepcopy(servers258.show_instance_action)
+create_backup = copy.deepcopy(servers258.create_backup)
diff --git a/tempest/lib/api_schema/response/compute/v2_6/servers.py b/tempest/lib/api_schema/response/compute/v2_6/servers.py
index 05ab616..d3fc884 100644
--- a/tempest/lib/api_schema/response/compute/v2_6/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_6/servers.py
@@ -33,6 +33,7 @@
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
create_backup = copy.deepcopy(servers.create_backup)
+list_live_migrations = copy.deepcopy(servers.list_live_migrations)
# NOTE: The consolidated remote console API got introduced with v2.6
# with bp/consolidate-console-api. See Nova commit 578bafeda
diff --git a/tempest/lib/api_schema/response/compute/v2_62/servers.py b/tempest/lib/api_schema/response/compute/v2_62/servers.py
index d761fe9..829479f 100644
--- a/tempest/lib/api_schema/response/compute/v2_62/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_62/servers.py
@@ -11,11 +11,11 @@
# under the License.
import copy
-from tempest.lib.api_schema.response.compute.v2_58 import servers as servers258
+from tempest.lib.api_schema.response.compute.v2_59 import servers as servers259
# microversion 2.62 added hostId and host to the event, but only hostId is
# mandatory
-show_instance_action = copy.deepcopy(servers258.show_instance_action)
+show_instance_action = copy.deepcopy(servers259.show_instance_action)
show_instance_action['response_body']['properties']['instanceAction'][
'properties']['events']['items'][
'properties']['hostId'] = {'type': 'string'}
@@ -27,22 +27,23 @@
# Below are the unchanged schema in this microversion. We need
# to keep this schema in this file to have the generic way to select the
# right schema based on self.schema_versions_info mapping in service client.
-list_servers = copy.deepcopy(servers258.list_servers)
-show_server_diagnostics = copy.deepcopy(servers258.show_server_diagnostics)
-get_remote_consoles = copy.deepcopy(servers258.get_remote_consoles)
-list_tags = copy.deepcopy(servers258.list_tags)
-update_all_tags = copy.deepcopy(servers258.update_all_tags)
-delete_all_tags = copy.deepcopy(servers258.delete_all_tags)
-check_tag_existence = copy.deepcopy(servers258.check_tag_existence)
-update_tag = copy.deepcopy(servers258.update_tag)
-delete_tag = copy.deepcopy(servers258.delete_tag)
-get_server = copy.deepcopy(servers258.get_server)
-list_servers_detail = copy.deepcopy(servers258.list_servers_detail)
-update_server = copy.deepcopy(servers258.update_server)
-rebuild_server = copy.deepcopy(servers258.rebuild_server)
+list_servers = copy.deepcopy(servers259.list_servers)
+show_server_diagnostics = copy.deepcopy(servers259.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers259.get_remote_consoles)
+list_tags = copy.deepcopy(servers259.list_tags)
+update_all_tags = copy.deepcopy(servers259.update_all_tags)
+delete_all_tags = copy.deepcopy(servers259.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers259.check_tag_existence)
+update_tag = copy.deepcopy(servers259.update_tag)
+delete_tag = copy.deepcopy(servers259.delete_tag)
+get_server = copy.deepcopy(servers259.get_server)
+list_servers_detail = copy.deepcopy(servers259.list_servers_detail)
+update_server = copy.deepcopy(servers259.update_server)
+rebuild_server = copy.deepcopy(servers259.rebuild_server)
rebuild_server_with_admin_pass = copy.deepcopy(
- servers258.rebuild_server_with_admin_pass)
-attach_volume = copy.deepcopy(servers258.attach_volume)
-show_volume_attachment = copy.deepcopy(servers258.show_volume_attachment)
-list_volume_attachments = copy.deepcopy(servers258.list_volume_attachments)
-create_backup = copy.deepcopy(servers258.create_backup)
+ servers259.rebuild_server_with_admin_pass)
+attach_volume = copy.deepcopy(servers259.attach_volume)
+show_volume_attachment = copy.deepcopy(servers259.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers259.list_volume_attachments)
+create_backup = copy.deepcopy(servers259.create_backup)
+list_live_migrations = copy.deepcopy(servers259.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_63/servers.py b/tempest/lib/api_schema/response/compute/v2_63/servers.py
index 865b4fd..fe596f5 100644
--- a/tempest/lib/api_schema/response/compute/v2_63/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_63/servers.py
@@ -78,3 +78,4 @@
list_volume_attachments = copy.deepcopy(servers262.list_volume_attachments)
show_instance_action = copy.deepcopy(servers262.show_instance_action)
create_backup = copy.deepcopy(servers262.create_backup)
+list_live_migrations = copy.deepcopy(servers262.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_70/servers.py b/tempest/lib/api_schema/response/compute/v2_70/servers.py
index 6bb688a..bafc7cb 100644
--- a/tempest/lib/api_schema/response/compute/v2_70/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_70/servers.py
@@ -80,3 +80,4 @@
delete_tag = copy.deepcopy(servers263.delete_tag)
show_instance_action = copy.deepcopy(servers263.show_instance_action)
create_backup = copy.deepcopy(servers263.create_backup)
+list_live_migrations = copy.deepcopy(servers263.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_71/servers.py b/tempest/lib/api_schema/response/compute/v2_71/servers.py
index b1c202b..6444e7b 100644
--- a/tempest/lib/api_schema/response/compute/v2_71/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_71/servers.py
@@ -84,3 +84,4 @@
list_volume_attachments = copy.deepcopy(servers270.list_volume_attachments)
show_instance_action = copy.deepcopy(servers270.show_instance_action)
create_backup = copy.deepcopy(servers270.create_backup)
+list_live_migrations = copy.deepcopy(servers270.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_73/servers.py b/tempest/lib/api_schema/response/compute/v2_73/servers.py
index 89f100d..e6ca52e 100644
--- a/tempest/lib/api_schema/response/compute/v2_73/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_73/servers.py
@@ -81,3 +81,4 @@
list_volume_attachments = copy.deepcopy(servers271.list_volume_attachments)
show_instance_action = copy.deepcopy(servers271.show_instance_action)
create_backup = copy.deepcopy(servers271.create_backup)
+list_live_migrations = copy.deepcopy(servers271.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_75/servers.py b/tempest/lib/api_schema/response/compute/v2_75/servers.py
index 6b3e93d..a06355b 100644
--- a/tempest/lib/api_schema/response/compute/v2_75/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_75/servers.py
@@ -62,3 +62,4 @@
list_volume_attachments = copy.deepcopy(servers273.list_volume_attachments)
show_instance_action = copy.deepcopy(servers273.show_instance_action)
create_backup = copy.deepcopy(servers273.create_backup)
+list_live_migrations = copy.deepcopy(servers273.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_79/servers.py b/tempest/lib/api_schema/response/compute/v2_79/servers.py
index 77d9beb..f2d3103 100644
--- a/tempest/lib/api_schema/response/compute/v2_79/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_79/servers.py
@@ -67,3 +67,4 @@
delete_tag = copy.deepcopy(servers275.delete_tag)
show_instance_action = copy.deepcopy(servers275.show_instance_action)
create_backup = copy.deepcopy(servers275.create_backup)
+list_live_migrations = copy.deepcopy(servers275.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_8/servers.py b/tempest/lib/api_schema/response/compute/v2_8/servers.py
index 366fb1b..0d37155 100644
--- a/tempest/lib/api_schema/response/compute/v2_8/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_8/servers.py
@@ -40,3 +40,4 @@
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
create_backup = copy.deepcopy(servers.create_backup)
+list_live_migrations = copy.deepcopy(servers.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_80/servers.py b/tempest/lib/api_schema/response/compute/v2_80/servers.py
new file mode 100644
index 0000000..cde1612
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_80/servers.py
@@ -0,0 +1,60 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_79 import servers as servers279
+
+###########################################################################
+#
+# 2.80:
+#
+# The user_id and project_id value is now returned in the response body in
+# addition to the migration id for the following API responses:
+#
+# - GET /os-migrations
+#
+###########################################################################
+
+list_live_migrations = copy.deepcopy(servers279.list_live_migrations)
+list_live_migrations['response_body']['properties']['migrations']['items'][
+ 'properties'].update({
+ 'user_id': {'type': 'string'},
+ 'project_id': {'type': 'string'}
+ })
+list_live_migrations['response_body']['properties']['migrations']['items'][
+ 'required'].extend(['user_id', 'project_id'])
+
+# NOTE(zhufl): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+# ****** Schemas unchanged since microversion 2.79 ***
+rebuild_server = copy.deepcopy(servers279.rebuild_server)
+rebuild_server_with_admin_pass = copy.deepcopy(
+ servers279.rebuild_server_with_admin_pass)
+update_server = copy.deepcopy(servers279.update_server)
+get_server = copy.deepcopy(servers279.get_server)
+list_servers_detail = copy.deepcopy(servers279.list_servers_detail)
+list_servers = copy.deepcopy(servers279.list_servers)
+show_server_diagnostics = copy.deepcopy(servers279.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers279.get_remote_consoles)
+list_tags = copy.deepcopy(servers279.list_tags)
+update_all_tags = copy.deepcopy(servers279.update_all_tags)
+delete_all_tags = copy.deepcopy(servers279.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers279.check_tag_existence)
+update_tag = copy.deepcopy(servers279.update_tag)
+delete_tag = copy.deepcopy(servers279.delete_tag)
+show_instance_action = copy.deepcopy(servers279.show_instance_action)
+create_backup = copy.deepcopy(servers279.create_backup)
+attach_volume = copy.deepcopy(servers279.attach_volume)
+show_volume_attachment = copy.deepcopy(servers279.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers279.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_89/servers.py b/tempest/lib/api_schema/response/compute/v2_89/servers.py
index debf0dc..f072eda 100644
--- a/tempest/lib/api_schema/response/compute/v2_89/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_89/servers.py
@@ -12,7 +12,7 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_79 import servers as servers279
+from tempest.lib.api_schema.response.compute.v2_80 import servers as servers280
###########################################################################
@@ -27,11 +27,11 @@
# - POST /servers/{server_id}/os-volume_attachments
###########################################################################
-attach_volume = copy.deepcopy(servers279.attach_volume)
+attach_volume = copy.deepcopy(servers280.attach_volume)
-show_volume_attachment = copy.deepcopy(servers279.show_volume_attachment)
+show_volume_attachment = copy.deepcopy(servers280.show_volume_attachment)
-list_volume_attachments = copy.deepcopy(servers279.list_volume_attachments)
+list_volume_attachments = copy.deepcopy(servers280.list_volume_attachments)
# Remove properties
# 'id' is available unti v2.88
@@ -64,21 +64,22 @@
# NOTE(zhufl): Below are the unchanged schema in this microversion. We
# need to keep this schema in this file to have the generic way to select the
# right schema based on self.schema_versions_info mapping in service client.
-# ****** Schemas unchanged since microversion 2.75 ***
-rebuild_server = copy.deepcopy(servers279.rebuild_server)
+# ****** Schemas unchanged since microversion 2.80 ***
+rebuild_server = copy.deepcopy(servers280.rebuild_server)
rebuild_server_with_admin_pass = copy.deepcopy(
- servers279.rebuild_server_with_admin_pass)
-update_server = copy.deepcopy(servers279.update_server)
-get_server = copy.deepcopy(servers279.get_server)
-list_servers_detail = copy.deepcopy(servers279.list_servers_detail)
-list_servers = copy.deepcopy(servers279.list_servers)
-show_server_diagnostics = copy.deepcopy(servers279.show_server_diagnostics)
-get_remote_consoles = copy.deepcopy(servers279.get_remote_consoles)
-list_tags = copy.deepcopy(servers279.list_tags)
-update_all_tags = copy.deepcopy(servers279.update_all_tags)
-delete_all_tags = copy.deepcopy(servers279.delete_all_tags)
-check_tag_existence = copy.deepcopy(servers279.check_tag_existence)
-update_tag = copy.deepcopy(servers279.update_tag)
-delete_tag = copy.deepcopy(servers279.delete_tag)
-show_instance_action = copy.deepcopy(servers279.show_instance_action)
-create_backup = copy.deepcopy(servers279.create_backup)
+ servers280.rebuild_server_with_admin_pass)
+update_server = copy.deepcopy(servers280.update_server)
+get_server = copy.deepcopy(servers280.get_server)
+list_servers_detail = copy.deepcopy(servers280.list_servers_detail)
+list_servers = copy.deepcopy(servers280.list_servers)
+show_server_diagnostics = copy.deepcopy(servers280.show_server_diagnostics)
+get_remote_consoles = copy.deepcopy(servers280.get_remote_consoles)
+list_tags = copy.deepcopy(servers280.list_tags)
+update_all_tags = copy.deepcopy(servers280.update_all_tags)
+delete_all_tags = copy.deepcopy(servers280.delete_all_tags)
+check_tag_existence = copy.deepcopy(servers280.check_tag_existence)
+update_tag = copy.deepcopy(servers280.update_tag)
+delete_tag = copy.deepcopy(servers280.delete_tag)
+show_instance_action = copy.deepcopy(servers280.show_instance_action)
+create_backup = copy.deepcopy(servers280.create_backup)
+list_live_migrations = copy.deepcopy(servers280.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
index b4c7865..ad39b14 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -59,3 +59,4 @@
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
create_backup = copy.deepcopy(servers.create_backup)
+list_live_migrations = copy.deepcopy(servers.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_96/servers.py b/tempest/lib/api_schema/response/compute/v2_96/servers.py
index 8a4ed9f..0c4be65 100644
--- a/tempest/lib/api_schema/response/compute/v2_96/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_96/servers.py
@@ -84,3 +84,4 @@
delete_tag = copy.deepcopy(servers289.delete_tag)
show_instance_action = copy.deepcopy(servers289.show_instance_action)
create_backup = copy.deepcopy(servers289.create_backup)
+list_live_migrations = copy.deepcopy(servers289.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_98/servers.py b/tempest/lib/api_schema/response/compute/v2_98/servers.py
index 2fca3eb..0296410 100644
--- a/tempest/lib/api_schema/response/compute/v2_98/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_98/servers.py
@@ -83,3 +83,4 @@
delete_tag = copy.deepcopy(servers296.delete_tag)
show_instance_action = copy.deepcopy(servers296.show_instance_action)
create_backup = copy.deepcopy(servers296.create_backup)
+list_live_migrations = copy.deepcopy(servers296.list_live_migrations)
diff --git a/tempest/lib/api_schema/response/compute/v2_99/servers.py b/tempest/lib/api_schema/response/compute/v2_99/servers.py
index e667321..25b3150 100644
--- a/tempest/lib/api_schema/response/compute/v2_99/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_99/servers.py
@@ -31,6 +31,7 @@
list_volume_attachments = copy.deepcopy(servers.list_volume_attachments)
show_instance_action = copy.deepcopy(servers.show_instance_action)
create_backup = copy.deepcopy(servers.create_backup)
+list_live_migrations = copy.deepcopy(servers.list_live_migrations)
console_auth_tokens = {
'status_code': [200],
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 4a607a3..1778194 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -43,6 +43,7 @@
from tempest.lib.api_schema.response.compute.v2_75 import servers as schemav275
from tempest.lib.api_schema.response.compute.v2_79 import servers as schemav279
from tempest.lib.api_schema.response.compute.v2_8 import servers as schemav28
+from tempest.lib.api_schema.response.compute.v2_80 import servers as schemav280
from tempest.lib.api_schema.response.compute.v2_89 import servers as schemav289
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
from tempest.lib.api_schema.response.compute.v2_96 import servers as schemav296
@@ -79,7 +80,8 @@
{'min': '2.71', 'max': '2.72', 'schema': schemav271},
{'min': '2.73', 'max': '2.74', 'schema': schemav273},
{'min': '2.75', 'max': '2.78', 'schema': schemav275},
- {'min': '2.79', 'max': '2.88', 'schema': schemav279},
+ {'min': '2.79', 'max': '2.79', 'schema': schemav279},
+ {'min': '2.80', 'max': '2.88', 'schema': schemav280},
{'min': '2.89', 'max': '2.95', 'schema': schemav289},
{'min': '2.96', 'max': '2.97', 'schema': schemav296},
{'min': '2.98', 'max': '2.98', 'schema': schemav298},
@@ -549,6 +551,35 @@
"""
return self.action(server_id, 'os-migrateLive', **kwargs)
+ def list_in_progress_live_migration(self, server_id, **kwargs):
+ """This should be called with administrator privileges.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/compute/#id318
+ """
+ resp, body = self.get('servers/%s/migrations' % server_id)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.list_live_migrations, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def force_complete_live_migration(self, server_id, migration_id, **kwargs):
+ """Force complete a in-progress live migration.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/compute/#force-migration-complete-action-force-complete-action
+ """
+ post_body = json.dumps({"force_complete": 'null'})
+ resp, body = self.post('servers/%s/migrations/%s/action' %
+ (server_id, migration_id),
+ post_body)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.server_actions_common_schema, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
def migrate_server(self, server_id, **kwargs):
"""Migrate a server to a new host.
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index f4ee98d..d8ffa54 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -33,6 +33,8 @@
class BaseTestNetworkAdvancedServerOps(manager.NetworkScenarioTest):
"""Base class for defining methods used in tests."""
+ credentials = ['primary', 'admin', 'project_manager']
+
@classmethod
def skip_checks(cls):
super(BaseTestNetworkAdvancedServerOps, cls).skip_checks()
@@ -47,7 +49,7 @@
@classmethod
def setup_clients(cls):
super(BaseTestNetworkAdvancedServerOps, cls).setup_clients()
- cls.admin_servers_client = cls.os_admin.servers_client
+ cls.mgr_server_client = cls.os_admin.servers_client
cls.sec_group_rules_client = \
cls.os_primary.security_group_rules_client
cls.sec_groups_client = cls.os_primary.security_groups_client
@@ -159,7 +161,13 @@
self._wait_server_status_and_check_network_connectivity(
server, keypair, floating_ip)
- self.admin_servers_client.migrate_server(
+ if (not dest_host and CONF.enforce_scope.nova and 'manager' in
+ CONF.compute_feature_enabled.nova_policy_roles):
+ self.mgr_server_client = self.os_project_manager.servers_client
+ LOG.info("Using project manager for migrating server: %s, "
+ "project manager user id: %s",
+ server['id'], self.mgr_server_client.user_id)
+ self.mgr_server_client.migrate_server(
server['id'], host=dest_host)
waiters.wait_for_server_status(self.servers_client, server['id'],
'VERIFY_RESIZE')
@@ -210,8 +218,13 @@
if dest_host:
migration_kwargs['host'] = dest_host
-
- self.admin_servers_client.live_migrate_server(
+ elif (CONF.enforce_scope.nova and 'manager' in
+ CONF.compute_feature_enabled.nova_policy_roles):
+ self.mgr_server_client = self.os_project_manager.servers_client
+ LOG.info("Using project manager for migrating server: %s, "
+ "project manager user id: %s",
+ server['id'], self.mgr_server_client.user_id)
+ self.mgr_server_client.live_migrate_server(
server['id'], **migration_kwargs)
waiters.wait_for_server_status(self.servers_client,
server['id'], 'ACTIVE')
@@ -260,7 +273,13 @@
self._wait_server_status_and_check_network_connectivity(
server, keypair, floating_ip)
- self.admin_servers_client.migrate_server(
+ if (not dest_host and CONF.enforce_scope.nova and 'manager' in
+ CONF.compute_feature_enabled.nova_policy_roles):
+ self.mgr_server_client = self.os_project_manager.servers_client
+ LOG.info("Using project manager for migrating server: %s, "
+ "project manager user id: %s",
+ server['id'], self.mgr_server_client.user_id)
+ self.mgr_server_client.migrate_server(
server['id'], host=dest_host)
waiters.wait_for_server_status(self.servers_client, server['id'],
'VERIFY_RESIZE')
@@ -415,7 +434,7 @@
- Cold Migration with revert
- Live Migration
"""
- credentials = ['primary', 'admin']
+ credentials = ['primary', 'admin', 'project_manager']
compute_min_microversion = "2.74"
@classmethod
@@ -441,7 +460,7 @@
cls.keypairs_client = cls.os_admin.keypairs_client
cls.floating_ips_client = cls.os_admin.floating_ips_client
cls.servers_client = cls.os_admin.servers_client
- cls.admin_servers_client = cls.os_admin.servers_client
+ cls.mgr_server_client = cls.os_admin.servers_client
@decorators.idempotent_id('06e23934-79ae-11ee-b962-0242ac120002')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 204471e..d53e918 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_log import log as logging
import testtools
from tempest.common import compute
@@ -23,6 +24,7 @@
from tempest.scenario import manager
CONF = config.CONF
+LOG = logging.getLogger(__name__)
class TestShelveInstance(manager.ScenarioTest):
@@ -38,12 +40,18 @@
"""
- credentials = ['primary', 'admin']
+ credentials = ['primary', 'admin', 'project_manager']
@classmethod
def setup_clients(cls):
super(TestShelveInstance, cls).setup_clients()
- cls.admin_servers_client = cls.os_admin.servers_client
+ cls.mgr_servers_client = cls.os_admin.servers_client
+ if (CONF.enforce_scope.nova and 'manager' in
+ CONF.compute_feature_enabled.nova_policy_roles):
+ cls.mgr_servers_client = cls.os_project_manager.servers_client
+ LOG.info("Using project manager for migrating server, "
+ "project manager user id: %s",
+ cls.mgr_servers_client.user_id)
@classmethod
def skip_checks(cls):
@@ -62,7 +70,7 @@
def _cold_migrate_server(self, server):
src_host = self.get_host_for_server(server['id'])
- self.admin_servers_client.migrate_server(server['id'])
+ self.mgr_servers_client.migrate_server(server['id'])
waiters.wait_for_server_status(self.servers_client,
server['id'], 'VERIFY_RESIZE')
self.servers_client.confirm_resize_server(server['id'])