Merge "Add more server migration tests"
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/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index fe8970f..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__)
@@ -416,3 +417,140 @@
             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/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.