Merge "Move Stable branch Policy Doc outside of coding guide"
diff --git a/.zuul.yaml b/.zuul.yaml
index 3fee6ff..8ab3028 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,7 +1,13 @@
 - job:
     name: devstack-tempest
     parent: devstack
-    description: Base Tempest job.
+    nodeset: openstack-single-node
+    description: |
+      Base Tempest job.
+
+      This Tempest job provides the base for both the single and multi-node
+      test setup. To run a multi-node test inherit from devstack-tempest and
+      set the nodeset to a multi-node one.
     required-projects:
       - openstack/tempest
     timeout: 7200
@@ -10,6 +16,11 @@
     vars:
       devstack_services:
         tempest: true
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            compute:
+              min_compute_nodes: "{{ groups['compute'] | default(['controller']) | length }}"
       test_results_stage_name: 'test_results'
       zuul_copy_output:
         '{{ devstack_base_dir }}/tempest/etc/tempest.conf': 'logs'
@@ -81,6 +92,36 @@
         # without Swift, c-bak cannot run (in the Gate at least)
         c-bak: false
 
+- job:
+    name: tempest-multinode-full
+    parent: devstack-tempest
+    nodeset: openstack-two-node
+    # Until the devstack changes are backported, only run this on master
+    branches:
+      - master
+    description: |
+      Base multinode integration test with Neutron networking and py27.
+      Former names for this job where:
+        * neutron-tempest-multinode-full
+        * legacy-tempest-dsvm-neutron-multinode-full
+        * gate-tempest-dsvm-neutron-multinode-full-ubuntu-xenial-nv
+      This job includes two nodes, controller / tempest plus a subnode, but
+      it can be used with different topologies, as long as a controller node
+      and a tempest one exist.
+    vars:
+      tox_envlist: full
+      devstack_localrc:
+        FORCE_CONFIG_DRIVE: False
+        NOVA_ALLOW_MOVE_TO_SAME_HOST: false
+        LIVE_MIGRATION_AVAILABLE: true
+        USE_BLOCK_MIGRATION_FOR_LIVE_MIGRATION: true
+    group-vars:
+      peers:
+        devstack_localrc:
+          NOVA_ALLOW_MOVE_TO_SAME_HOST: false
+          LIVE_MIGRATION_AVAILABLE: true
+          USE_BLOCK_MIGRATION_FOR_LIVE_MIGRATION: true
+
 - nodeset:
     name: openstack-bionic-node
     nodes:
@@ -284,6 +325,16 @@
               - ^setup.cfg$
               - ^tempest/hacking/.*$
               - ^tempest/tests/.*$
+        - tempest-multinode-full:
+            irrelevant-files:
+              - ^(test-|)requirements.txt$
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
         - tempest-tox-plugin-sanity-check
         - tempest-scenario-multinode-lvm-multibackend:
             voting: false
@@ -296,21 +347,21 @@
               - ^setup.cfg$
               - ^tempest/hacking/.*$
               - ^tempest/tests/.*$
+        - nova-cells-v1:
+            irrelevant-files:
+              - ^(test-|)requirements.txt$
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
     gate:
       jobs:
         - nova-multiattach
     experimental:
       jobs:
-        - nova-cells-v1:
-            irrelevant-files:
-              - ^(test-|)requirements.txt$
-              - ^.*\.rst$
-              - ^doc/.*$
-              - ^etc/.*$
-              - ^releasenotes/.*$
-              - ^setup.cfg$
-              - ^tempest/hacking/.*$
-              - ^tempest/tests/.*$
         - nova-live-migration:
             irrelevant-files:
               - ^(test-|)requirements.txt$
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index f2c51b3..ea868ae 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -372,7 +372,11 @@
 
   * `2.60`_
 
-  .. _2.60: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id54
+  .. _2.60: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-queens
+
+  * `2.63`_
+
+  .. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id57
 
 * Volume
 
diff --git a/playbooks/devstack-tempest.yaml b/playbooks/devstack-tempest.yaml
index a684984..01155a8 100644
--- a/playbooks/devstack-tempest.yaml
+++ b/playbooks/devstack-tempest.yaml
@@ -3,7 +3,7 @@
 # avoid zuul retrying on legitimate failures.
 - hosts: all
   roles:
-    - run-devstack
+    - orchestrate-devstack
 
 # We run tests only on one node, regardless how many nodes are in the system
 - hosts: tempest
diff --git a/playbooks/post-tempest.yaml b/playbooks/post-tempest.yaml
index 4dde2c9..6e0bcad 100644
--- a/playbooks/post-tempest.yaml
+++ b/playbooks/post-tempest.yaml
@@ -1,4 +1,4 @@
-- hosts: all
+- hosts: tempest
   become: true
   roles:
     - role: fetch-subunit-output
diff --git a/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml b/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml
new file mode 100644
index 0000000..03d0ae8
--- /dev/null
+++ b/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Add ``enable_service``, ``disable_service`` , ``disable_log_reason``,
+    ``freeze_host`` and ``thaw_host`` API endpoints to volume v3
+    ``services_client``.
diff --git a/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml b/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
index b54ee8b..19d47d1 100644
--- a/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
+++ b/releasenotes/notes/add-port-profile-config-option-2610b2fa67027960.yaml
@@ -1,11 +1,9 @@
 ---
-prelude: >
-    When using OVS HW offload feature we need to create
-    Neutron port with a certain capability. This is done
-    by creating Neutron port with binding profile. To be
-    able to test this we need profile capability support
-    in Tempest as well.
 features:
   - A new config option 'port_profile' is added to the section
     'network' to specify capabilities of the port.
-    By default this is set to {}.
+    By default this is set to {}. When using OVS HW offload
+    feature we need to create Neutron port with a certain
+    capability. This is done by creating Neutron port with
+    binding profile. To be able to test this we need profile
+    capability support in Tempest as well.
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index ec7b505..9fc5af0 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -393,7 +393,7 @@
         resp = self.client.create_backup(self.server_id,
                                          backup_type='daily',
                                          rotation=2,
-                                         name=backup1).response
+                                         name=backup1)
         oldest_backup_exist = True
 
         # the oldest one should be deleted automatically in this test
@@ -409,10 +409,10 @@
                                 "deleted during rotation.", oldest_backup)
 
         if api_version_utils.compare_version_header_to_response(
-                "OpenStack-API-Version", "compute 2.45", resp, "lt"):
+                "OpenStack-API-Version", "compute 2.45", resp.response, "lt"):
             image1_id = resp['image_id']
         else:
-            image1_id = data_utils.parse_image_id(resp['location'])
+            image1_id = data_utils.parse_image_id(resp.response['location'])
         self.addCleanup(_clean_oldest_backup, image1_id)
         waiters.wait_for_image_status(glance_client,
                                       image1_id, 'active')
@@ -422,12 +422,12 @@
         resp = self.client.create_backup(self.server_id,
                                          backup_type='daily',
                                          rotation=2,
-                                         name=backup2).response
+                                         name=backup2)
         if api_version_utils.compare_version_header_to_response(
-                "OpenStack-API-Version", "compute 2.45", resp, "lt"):
+                "OpenStack-API-Version", "compute 2.45", resp.response, "lt"):
             image2_id = resp['image_id']
         else:
-            image2_id = data_utils.parse_image_id(resp['location'])
+            image2_id = data_utils.parse_image_id(resp.response['location'])
         self.addCleanup(glance_client.delete_image, image2_id)
         waiters.wait_for_image_status(glance_client,
                                       image2_id, 'active')
@@ -465,12 +465,12 @@
         resp = self.client.create_backup(self.server_id,
                                          backup_type='daily',
                                          rotation=2,
-                                         name=backup3).response
+                                         name=backup3)
         if api_version_utils.compare_version_header_to_response(
-                "OpenStack-API-Version", "compute 2.45", resp, "lt"):
+                "OpenStack-API-Version", "compute 2.45", resp.response, "lt"):
             image3_id = resp['image_id']
         else:
-            image3_id = data_utils.parse_image_id(resp['location'])
+            image3_id = data_utils.parse_image_id(resp.response['location'])
         self.addCleanup(glance_client.delete_image, image3_id)
         # the first back up should be deleted
         waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 543fa1c..56d973e 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -209,3 +209,34 @@
                                        server['id'], 'ACTIVE')
         # Checking list details API response schema
         self.servers_client.list_servers(detail=True)
+
+
+class ServerShowV263Test(base.BaseV2ComputeTest):
+    min_microversion = '2.63'
+    max_microversion = 'latest'
+
+    @decorators.idempotent_id('71b8e3d5-11d2-494f-b917-b094a4afed3c')
+    def test_show_update_rebuild_list_server(self):
+        trusted_certs = ['test-cert-1', 'test-cert-2']
+        server = self.create_test_server(
+            trusted_image_certificates=trusted_certs,
+            wait_until='ACTIVE')
+
+        # Check show API response schema
+        self.servers_client.show_server(server['id'])['server']
+
+        # Check update API response schema
+        self.servers_client.update_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
+
+        # Check rebuild API response schema
+        self.servers_client.rebuild_server(server['id'], self.image_ref_alt)
+        waiters.wait_for_server_status(self.servers_client,
+                                       server['id'], 'ACTIVE')
+
+        # Check list details API response schema
+        params = {'trusted_image_certificates': trusted_certs}
+        servers = self.servers_client.list_servers(
+            detail=True, **params)['servers']
+        self.assertNotEmpty(servers)
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 69cac33..62ced19 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -384,12 +384,23 @@
 
         rules = self.roles_client.list_all_role_inference_rules()[
             'role_inferences']
+
+        # NOTE(jaosorior): With the work related to the define-default-roles
+        # blueprint, we now have 'admin', 'member' and 'reader' by default. So
+        # we filter every other implied role to only take into account the ones
+        # relates to this test.
+        relevant_roles = (self.roles[0]['id'], self.roles[1]['id'],
+                          self.roles[2]['id'], self.role['id'])
+
+        def is_implied_role_relevant(rule):
+            return any(r for r in rule['implies'] if r['id'] in relevant_roles)
+
+        relevant_rules = filter(is_implied_role_relevant, rules)
         # Sort the rules by the number of inferences, since there should be 1
         # inference between "roles[2]" and "role" and 2 inferences for
         # "roles[0]": between "roles[1]" and "roles[2]".
-        sorted_rules = sorted(rules, key=lambda r: len(r['implies']))
+        sorted_rules = sorted(relevant_rules, key=lambda r: len(r['implies']))
 
-        # Check that 2 sets of rules are returned.
         self.assertEqual(2, len(sorted_rules))
         # Check that only 1 inference rule exists between "roles[2]" and "role"
         self.assertEqual(1, len(sorted_rules[0]['implies']))
diff --git a/tempest/api/volume/admin/test_group_snapshots.py b/tempest/api/volume/admin/test_group_snapshots.py
index 45f4caa..731a055 100644
--- a/tempest/api/volume/admin/test_group_snapshots.py
+++ b/tempest/api/volume/admin/test_group_snapshots.py
@@ -157,6 +157,57 @@
         waiters.wait_for_volume_resource_status(
             self.groups_client, grp2['id'], 'available')
 
+    @decorators.idempotent_id('7d7fc000-0b4c-4376-a372-544116d2e127')
+    @decorators.related_bug('1739031')
+    def test_delete_group_snapshots_following_updated_volumes(self):
+        volume_type = self.create_volume_type()
+
+        group_type = self.create_group_type()
+
+        # Create a volume group
+        grp = self.create_group(group_type=group_type['id'],
+                                volume_types=[volume_type['id']])
+
+        # Note: When dealing with consistency groups all volumes must
+        # reside on the same backend. Adding volumes to the same consistency
+        # group from multiple backends isn't supported. In order to ensure all
+        # volumes share the same backend, all volumes must share same
+        # volume-type and group id.
+        volume_list = []
+        for _ in range(2):
+            volume = self.create_volume(volume_type=volume_type['id'],
+                                        group_id=grp['id'])
+            volume_list.append(volume['id'])
+
+        for vol in volume_list:
+            self.groups_client.update_group(grp['id'],
+                                            remove_volumes=vol)
+            waiters.wait_for_volume_resource_status(
+                self.groups_client, grp['id'], 'available')
+
+            self.groups_client.update_group(grp['id'],
+                                            add_volumes=vol)
+            waiters.wait_for_volume_resource_status(
+                self.groups_client, grp['id'], 'available')
+
+        # Verify the created volumes are associated with consistency group
+        vols = self.volumes_client.list_volumes(detail=True)['volumes']
+        grp_vols = [v for v in vols if v['group_id'] == grp['id']]
+        self.assertEqual(2, len(grp_vols))
+
+        # Create a snapshot group
+        group_snapshot = self._create_group_snapshot(group_id=grp['id'])
+        snapshots = self.snapshots_client.list_snapshots(
+            detail=True)['snapshots']
+
+        for snap in snapshots:
+            if snap['volume_id'] in volume_list:
+                waiters.wait_for_volume_resource_status(
+                    self.snapshots_client, snap['id'], 'available')
+
+        # Delete a snapshot group
+        self._delete_group_snapshot(group_snapshot)
+
 
 class GroupSnapshotsV319Test(BaseGroupSnapshotsTest):
     _api_version = 3
diff --git a/tempest/api/volume/admin/test_volume_services_negative.py b/tempest/api/volume/admin/test_volume_services_negative.py
new file mode 100644
index 0000000..6f3dbc6
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_services_negative.py
@@ -0,0 +1,65 @@
+# Copyright 2018 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+#    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.
+
+from tempest.api.volume import base
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+
+class VolumeServicesNegativeTest(base.BaseVolumeAdminTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(VolumeServicesNegativeTest, cls).resource_setup()
+        cls.services = cls.admin_volume_services_client.list_services()[
+            'services']
+        cls.host = cls.services[0]['host']
+        cls.binary = cls.services[0]['binary']
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('3246ce65-ba70-4159-aa3b-082c28e4b484')
+    def test_enable_service_with_invalid_host(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.admin_volume_services_client.enable_service,
+                          host='invalid_host', binary=self.binary)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('c571f179-c6e6-4c50-a0ab-368b628a8ac1')
+    def test_disable_service_with_invalid_binary(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.admin_volume_services_client.disable_service,
+                          host=self.host, binary='invalid_binary')
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('77767b36-5e8f-4c68-a0b5-2308cc21ec64')
+    def test_disable_log_reason_with_no_reason(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.admin_volume_services_client.disable_log_reason,
+                          host=self.host, binary=self.binary,
+                          disabled_reason=None)
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('712bfab8-1f44-4eb5-a632-fa70bf78f05e')
+    def test_freeze_host_with_invalid_host(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.admin_volume_services_client.freeze_host,
+                          host='invalid_host')
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('7c6287c9-d655-47e1-9a11-76f6657a6dce')
+    def test_thaw_host_with_invalid_host(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.admin_volume_services_client.thaw_host,
+                          host='invalid_host')
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 35eea11..45060d0 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -60,6 +60,8 @@
         # Create backup
         backup_name = data_utils.rand_name(self.__class__.__name__ + '-Backup')
         backup = self.create_backup(volume_id=volume['id'], name=backup_name)
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
         self.assertEqual(backup_name, backup['name'])
 
         # Export Backup
@@ -105,16 +107,18 @@
         self.addCleanup(self.volumes_client.delete_volume,
                         restore['volume_id'])
         self.assertEqual(backup['id'], restore['backup_id'])
-        waiters.wait_for_volume_resource_status(self.volumes_client,
-                                                restore['volume_id'],
-                                                'available')
+
+        # When restore operation is performed then, backup['id']
+        # goes to 'restoring' state so we need to wait for
+        # backup['id'] to become 'available'.
+        waiters.wait_for_volume_resource_status(
+            self.backups_client, backup['id'], 'available')
+        waiters.wait_for_volume_resource_status(
+            self.volumes_client, restore['volume_id'], 'available')
 
         # Verify if restored volume is there in volume list
         volumes = self.volumes_client.list_volumes()['volumes']
         self.assertIn(restore['volume_id'], [v['id'] for v in volumes])
-        waiters.wait_for_volume_resource_status(self.admin_backups_client,
-                                                import_backup['id'],
-                                                'available')
 
     @decorators.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94')
     def test_volume_backup_reset_status(self):
@@ -124,6 +128,8 @@
         backup_name = data_utils.rand_name(
             self.__class__.__name__ + '-Backup')
         backup = self.create_backup(volume_id=volume['id'], name=backup_name)
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
         self.assertEqual(backup_name, backup['name'])
         # Reset backup status to error
         self.admin_backups_client.reset_backup_status(backup_id=backup['id'],
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 07cfad5..c178272 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -117,6 +117,8 @@
             self.__class__.__name__ + '-Backup')
         backup = self.create_backup(volume_id=volume['id'],
                                     name=backup_name, force=True)
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'in-use')
         self.assertEqual(backup_name, backup['name'])
 
     @decorators.idempotent_id('2a8ba340-dff2-4511-9db7-646f07156b15')
@@ -132,6 +134,8 @@
 
         # Create a backup
         backup = self.create_backup(volume_id=volume['id'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
 
         # Restore the backup
         restored_volume_id = self.restore_backup(backup['id'])['volume_id']
@@ -160,6 +164,8 @@
         # Create volume and backup
         volume = self.create_volume()
         backup = self.create_backup(volume_id=volume['id'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
 
         # Update backup and assert response body for update_backup method
         update_kwargs = {
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 52114bc..93638b8 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -15,6 +15,7 @@
 
 from tempest.api.volume import base
 from tempest.common import utils
+from tempest.common import waiters
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -163,6 +164,8 @@
 
         backup = self.create_backup(volume_id=self.volume_origin['id'],
                                     snapshot_id=snapshot['id'])
+        waiters.wait_for_volume_resource_status(self.snapshots_client,
+                                                snapshot['id'], 'available')
         backup_info = self.backups_client.show_backup(backup['id'])['backup']
         self.assertEqual(self.volume_origin['id'], backup_info['volume_id'])
         self.assertEqual(snapshot['id'], backup_info['snapshot_id'])
diff --git a/tempest/lib/api_schema/response/compute/v2_63/__init__.py b/tempest/lib/api_schema/response/compute/v2_63/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_63/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_63/servers.py b/tempest/lib/api_schema/response/compute/v2_63/servers.py
new file mode 100644
index 0000000..5cdaf54
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_63/servers.py
@@ -0,0 +1,65 @@
+#    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_26 import servers as servers226
+from tempest.lib.api_schema.response.compute.v2_54 import servers as servers254
+from tempest.lib.api_schema.response.compute.v2_57 import servers as servers257
+
+# Nova microversion 2.63 adds 'trusted_image_certificates' (a list of
+# certificate IDs) to the server rebuild and servers details responses.
+
+
+trusted_certs = {
+    'type': ['array', 'null'],
+    'minItems': 1,
+    'maxItems': 50,
+    'uniqueItems': True,
+    'items': {
+        'type': 'string',
+        'minLength': 1
+    }
+}
+# list response schema wasn't changed for v2.63 so use v2.26
+list_servers = copy.deepcopy(servers226.list_servers)
+
+list_servers_detail = copy.deepcopy(servers254.list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+list_servers_detail['response_body']['properties']['servers']['items'][
+    'required'].append('trusted_image_certificates')
+
+rebuild_server = copy.deepcopy(servers257.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+rebuild_server['response_body']['properties']['server'][
+    'required'].append('trusted_image_certificates')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+    servers257.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+    'required'].append('trusted_image_certificates')
+
+update_server = copy.deepcopy(servers254.update_server)
+update_server['response_body']['properties']['server'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+update_server['response_body']['properties']['server'][
+    'required'].append('trusted_image_certificates')
+
+get_server = copy.deepcopy(servers254.get_server)
+get_server['response_body']['properties']['server'][
+    'properties'].update({'trusted_image_certificates': trusted_certs})
+get_server['response_body']['properties']['server'][
+    'required'].append('trusted_image_certificates')
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index f6e0e69..0314356 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -32,6 +32,7 @@
 from tempest.lib.api_schema.response.compute.v2_54 import servers as schemav254
 from tempest.lib.api_schema.response.compute.v2_57 import servers as schemav257
 from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26
+from tempest.lib.api_schema.response.compute.v2_63 import servers as schemav263
 from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
@@ -51,7 +52,8 @@
         {'min': '2.47', 'max': '2.47', 'schema': schemav247},
         {'min': '2.48', 'max': '2.53', 'schema': schemav248},
         {'min': '2.54', 'max': '2.56', 'schema': schemav254},
-        {'min': '2.57', 'max': None, 'schema': schemav257}]
+        {'min': '2.57', 'max': '2.62', 'schema': schemav257},
+        {'min': '2.63', 'max': None, 'schema': schemav263}]
 
     def __init__(self, auth_provider, service, region,
                  enable_instance_password=True, **kwargs):
diff --git a/tempest/lib/services/volume/v1/encryption_types_client.py b/tempest/lib/services/volume/v1/encryption_types_client.py
index 0fac6bd..1fde79f 100644
--- a/tempest/lib/services/volume/v1/encryption_types_client.py
+++ b/tempest/lib/services/volume/v1/encryption_types_client.py
@@ -38,7 +38,7 @@
     def show_encryption_type(self, volume_type_id):
         """Get the volume encryption type for the specified volume type.
 
-        volume_type_id: Id of volume_type.
+        :param volume_type_id: Id of volume type.
         """
         url = "/types/%s/encryption" % volume_type_id
         resp, body = self.get(url)
@@ -61,7 +61,7 @@
         return rest_client.ResponseBody(resp, body)
 
     def delete_encryption_type(self, volume_type_id):
-        """Delete the encryption type for the specified volume-type."""
+        """Delete the encryption type for the specified volume type."""
         resp, body = self.delete(
             "/types/%s/encryption/provider" % volume_type_id)
         self.expected_success(202, resp.status)
diff --git a/tempest/lib/services/volume/v3/encryption_types_client.py b/tempest/lib/services/volume/v3/encryption_types_client.py
index 7443a87..03de187 100644
--- a/tempest/lib/services/volume/v3/encryption_types_client.py
+++ b/tempest/lib/services/volume/v3/encryption_types_client.py
@@ -38,7 +38,7 @@
     def show_encryption_type(self, volume_type_id):
         """Get the volume encryption type for the specified volume type.
 
-        volume_type_id: Id of volume_type.
+        :param volume_type_id: Id of volume type.
         """
         url = "/types/%s/encryption" % volume_type_id
         resp, body = self.get(url)
diff --git a/tempest/lib/services/volume/v3/services_client.py b/tempest/lib/services/volume/v3/services_client.py
index 09036a4..22155a9 100644
--- a/tempest/lib/services/volume/v3/services_client.py
+++ b/tempest/lib/services/volume/v3/services_client.py
@@ -20,9 +20,15 @@
 
 
 class ServicesClient(rest_client.RestClient):
-    """Client class to send CRUD Volume API requests"""
+    """Client class to send CRUD Volume Services API requests"""
 
     def list_services(self, **params):
+        """List all Cinder services.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#list-all-cinder-services
+        """
         url = 'os-services'
         if params:
             url += '?%s' % urllib.urlencode(params)
@@ -31,3 +37,66 @@
         body = json.loads(body)
         self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def enable_service(self, **kwargs):
+        """Enable service on a host.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#enable-a-cinder-service
+        """
+        put_body = json.dumps(kwargs)
+        resp, body = self.put('os-services/enable', put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def disable_service(self, **kwargs):
+        """Disable service on a host.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#disable-a-cinder-service
+        """
+        put_body = json.dumps(kwargs)
+        resp, body = self.put('os-services/disable', put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def disable_log_reason(self, **kwargs):
+        """Disable scheduling for a volume service and log disabled reason.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#log-disabled-cinder-service-information
+        """
+        put_body = json.dumps(kwargs)
+        resp, body = self.put('os-services/disable-log-reason', put_body)
+        body = json.loads(body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def freeze_host(self, **kwargs):
+        """Freeze a Cinder backend host.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#freeze-a-cinder-backend-host
+        """
+        put_body = json.dumps(kwargs)
+        resp, _ = self.put('os-services/freeze', put_body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp)
+
+    def thaw_host(self, **kwargs):
+        """Thaw a Cinder backend host.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#thaw-a-cinder-backend-host
+        """
+        put_body = json.dumps(kwargs)
+        resp, _ = self.put('os-services/thaw', put_body)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py
index 298925a..f79bcd8 100644
--- a/tempest/lib/services/volume/v3/snapshots_client.py
+++ b/tempest/lib/services/volume/v3/snapshots_client.py
@@ -176,11 +176,12 @@
         return rest_client.ResponseBody(resp, body)
 
     def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
-        """Update metadata item for the snapshot."""
-        # TODO(piyush): Current api-site doesn't contain this API description.
-        # After fixing the api-site, we need to fix here also for putting the
-        # link to api-site.
-        # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1529064
+        """Update metadata for the snapshot for a specific key.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#update-a-snapshot-s-metadata-for-a-specific-key
+        """
         put_body = json.dumps(kwargs)
         url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
         resp, body = self.put(url, put_body)
diff --git a/tempest/lib/services/volume/v3/types_client.py b/tempest/lib/services/volume/v3/types_client.py
index 6d9d03a..13ecd15 100644
--- a/tempest/lib/services/volume/v3/types_client.py
+++ b/tempest/lib/services/volume/v3/types_client.py
@@ -21,7 +21,7 @@
 
 
 class TypesClient(rest_client.RestClient):
-    """Client class to send CRUD Volume API requests"""
+    """Client class to send CRUD Volume Types API requests"""
 
     def is_resource_deleted(self, id):
         try:
@@ -36,7 +36,7 @@
         return 'volume-type'
 
     def list_volume_types(self, **params):
-        """List all the volume_types created.
+        """List all the volume types created.
 
         For a full list of available parameters, please refer to the official
         API reference:
@@ -52,7 +52,7 @@
         return rest_client.ResponseBody(resp, body)
 
     def show_volume_type(self, volume_type_id):
-        """Returns the details of a single volume_type.
+        """Returns the details of a single volume type.
 
         For a full list of available parameters, please refer to the official
         API reference:
@@ -78,7 +78,7 @@
         return rest_client.ResponseBody(resp, body)
 
     def delete_volume_type(self, volume_type_id):
-        """Deletes the Specified Volume_type.
+        """Deletes the specified volume type.
 
         For a full list of available parameters, please refer to the official
         API reference:
@@ -89,11 +89,11 @@
         return rest_client.ResponseBody(resp, body)
 
     def list_volume_types_extra_specs(self, volume_type_id, **params):
-        """List all the volume_types extra specs created.
+        """List all the volume type extra specs created.
 
-        TODO: Current api-site doesn't contain this API description.
-        After fixing the api-site, we need to fix here also for putting
-        the link to api-site.
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#show-all-extra-specifications-for-volume-type
         """
         url = 'types/%s/extra_specs' % volume_type_id
         if params:
@@ -105,7 +105,7 @@
         return rest_client.ResponseBody(resp, body)
 
     def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name):
-        """Returns the details of a single volume_type extra spec."""
+        """Returns the details of a single volume type extra spec."""
         url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name)
         resp, body = self.get(url)
         body = json.loads(body)
@@ -113,10 +113,10 @@
         return rest_client.ResponseBody(resp, body)
 
     def create_volume_type_extra_specs(self, volume_type_id, extra_specs):
-        """Creates a new Volume_type extra spec.
+        """Creates new volume type extra specs.
 
-        volume_type_id: Id of volume_type.
-        extra_specs: A dictionary of values to be used as extra_specs.
+        :param volume_type_id: Id of volume type.
+        :param extra_specs: A dictionary of values to be used as extra_specs.
         """
         url = "types/%s/extra_specs" % volume_type_id
         post_body = json.dumps({'extra_specs': extra_specs})
@@ -126,7 +126,7 @@
         return rest_client.ResponseBody(resp, body)
 
     def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name):
-        """Deletes the Specified Volume_type extra spec."""
+        """Deletes the specified volume type extra spec."""
         resp, body = self.delete("types/%s/extra_specs/%s" % (
             volume_type_id, extra_spec_name))
         self.expected_success(202, resp.status)
@@ -149,10 +149,10 @@
                                        extra_specs):
         """Update a volume_type extra spec.
 
-        volume_type_id: Id of volume_type.
-        extra_spec_name: Name of the extra spec to be updated.
-        extra_spec: A dictionary of with key as extra_spec_name and the
-                     updated value.
+        :param volume_type_id: Id of volume type.
+        :param extra_spec_name: Name of the extra spec to be updated.
+        :param extra_specs: A dictionary of with key as extra_spec_name and the
+                            updated value.
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-extra-specification-for-volume-type
diff --git a/tempest/scenario/test_volume_backup_restore.py b/tempest/scenario/test_volume_backup_restore.py
index c23b564..8a8c54e 100644
--- a/tempest/scenario/test_volume_backup_restore.py
+++ b/tempest/scenario/test_volume_backup_restore.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 from tempest.common import utils
+from tempest.common import waiters
 from tempest import config
 from tempest.lib import decorators
 from tempest.scenario import manager
@@ -56,6 +57,8 @@
 
         # Create a backup
         backup = self.create_backup(volume_id=volume['id'])
+        waiters.wait_for_volume_resource_status(self.volumes_client,
+                                                volume['id'], 'available')
 
         # Restore the backup
         restored_volume_id = self.restore_backup(backup['id'])['volume_id']
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index eb1e2b6..938d226 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -75,61 +75,76 @@
 
 
 class TestInterfaceWaiters(base.TestCase):
-    def setUp(self):
-        super(TestInterfaceWaiters, self).setUp()
-        self.client = mock.MagicMock()
-        self.client.build_timeout = 1
-        self.client.build_interval = 1
 
-    def _port_down(self):
-        return {'interfaceAttachment': {'port_state': 'DOWN'}}
+    build_timeout = 1.
+    build_interval = 1
+    port_down = {'interfaceAttachment': {'port_state': 'DOWN'}}
+    port_active = {'interfaceAttachment': {'port_state': 'ACTIVE'}}
 
-    def _port_active(self):
-        return {'interfaceAttachment': {'port_state': 'ACTIVE'}}
+    def mock_client(self, **kwargs):
+        return mock.MagicMock(
+            build_timeout=self.build_timeout,
+            build_interval=self.build_interval,
+            **kwargs)
 
     def test_wait_for_interface_status(self):
-        self.client.show_interface.side_effect = [self._port_down(),
-                                                  self._port_active()]
-        with mock.patch.object(time, 'sleep') as sleep_mock:
-            start_time = int(time.time())
-            waiters.wait_for_interface_status(self.client, 'server_id',
-                                              'port_id', 'ACTIVE')
-            end_time = int(time.time())
-            self.assertLess(end_time, (start_time + self.client.build_timeout))
-            sleep_mock.assert_called_once_with(self.client.build_interval)
+        show_interface = mock.Mock(
+            side_effect=[self.port_down, self.port_active])
+        client = self.mock_client(show_interface=show_interface)
+        self.patch('time.time', return_value=0.)
+        sleep = self.patch('time.sleep')
+
+        result = waiters.wait_for_interface_status(
+            client, 'server_id', 'port_id', 'ACTIVE')
+
+        self.assertIs(self.port_active['interfaceAttachment'], result)
+        show_interface.assert_has_calls([mock.call('server_id', 'port_id'),
+                                         mock.call('server_id', 'port_id')])
+        sleep.assert_called_once_with(client.build_interval)
 
     def test_wait_for_interface_status_timeout(self):
-        time_mock = self.patch('time.time')
-        time_mock.side_effect = utils.generate_timeout_series(1)
+        show_interface = mock.MagicMock(return_value=self.port_down)
+        client = self.mock_client(show_interface=show_interface)
+        self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
+        sleep = self.patch('time.sleep')
 
-        self.client.show_interface.return_value = self._port_down()
         self.assertRaises(lib_exc.TimeoutException,
                           waiters.wait_for_interface_status,
-                          self.client, 'server_id', 'port_id', 'ACTIVE')
+                          client, 'server_id', 'port_id', 'ACTIVE')
 
-    def _one_interface(self):
-        return {'interfaceAttachments': [{'port_id': 'port_one'}]}
+        show_interface.assert_has_calls([mock.call('server_id', 'port_id'),
+                                         mock.call('server_id', 'port_id')])
+        sleep.assert_called_once_with(client.build_interval)
 
-    def _two_interfaces(self):
-        return {'interfaceAttachments': [{'port_id': 'port_one'},
-                                         {'port_id': 'port_two'}]}
+    one_interface = {'interfaceAttachments': [{'port_id': 'port_one'}]}
+    two_interfaces = {'interfaceAttachments': [{'port_id': 'port_one'},
+                                               {'port_id': 'port_two'}]}
 
     def test_wait_for_interface_detach(self):
-        self.client.list_interfaces.side_effect = [self._two_interfaces(),
-                                                   self._one_interface()]
-        with mock.patch.object(time, 'sleep') as sleep_mock:
-            start_time = int(time.time())
-            waiters.wait_for_interface_detach(self.client, 'server_id',
-                                              'port_two')
-            end_time = int(time.time())
-            self.assertLess(end_time, (start_time + self.client.build_timeout))
-            sleep_mock.assert_called_once_with(self.client.build_interval)
+        list_interfaces = mock.MagicMock(
+            side_effect=[self.two_interfaces, self.one_interface])
+        client = self.mock_client(list_interfaces=list_interfaces)
+        self.patch('time.time', return_value=0.)
+        sleep = self.patch('time.sleep')
+
+        result = waiters.wait_for_interface_detach(
+            client, 'server_id', 'port_two')
+
+        self.assertIs(self.one_interface['interfaceAttachments'], result)
+        list_interfaces.assert_has_calls([mock.call('server_id'),
+                                          mock.call('server_id')])
+        sleep.assert_called_once_with(client.build_interval)
 
     def test_wait_for_interface_detach_timeout(self):
-        time_mock = self.patch('time.time')
-        time_mock.side_effect = utils.generate_timeout_series(1)
+        list_interfaces = mock.MagicMock(return_value=self.one_interface)
+        client = self.mock_client(list_interfaces=list_interfaces)
+        self.patch('time.time', side_effect=[0., client.build_timeout + 1.])
+        sleep = self.patch('time.sleep')
 
-        self.client.list_interfaces.return_value = self._one_interface()
         self.assertRaises(lib_exc.TimeoutException,
                           waiters.wait_for_interface_detach,
-                          self.client, 'server_id', 'port_one')
+                          client, 'server_id', 'port_one')
+
+        list_interfaces.assert_has_calls([mock.call('server_id'),
+                                          mock.call('server_id')])
+        sleep.assert_called_once_with(client.build_interval)
diff --git a/tempest/tests/lib/services/volume/v3/test_services_client.py b/tempest/tests/lib/services/volume/v3/test_services_client.py
new file mode 100644
index 0000000..f65228f
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_services_client.py
@@ -0,0 +1,214 @@
+# Copyright 2018 FiberHome Telecommunication Technologies CO.,LTD
+# All Rights Reserved.
+#
+#    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
+
+import mock
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.services.volume.v3 import services_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestServicesClient(base.BaseServiceTest):
+
+    FAKE_SERVICE_LIST = {
+        "services": [
+            {
+                "status": "enabled",
+                "binary": "cinder-backup",
+                "zone": "nova",
+                "state": "up",
+                "updated_at": "2017-07-20T07:20:17.000000",
+                "host": "fake-host",
+                "disabled_reason": None
+            },
+            {
+                "status": "enabled",
+                "binary": "cinder-scheduler",
+                "zone": "nova",
+                "state": "up",
+                "updated_at": "2017-07-20T07:20:24.000000",
+                "host": "fake-host",
+                "disabled_reason": None
+            },
+            {
+                "status": "enabled",
+                "binary": "cinder-volume",
+                "zone": "nova",
+                "frozen": False,
+                "state": "up",
+                "updated_at": "2017-07-20T07:20:20.000000",
+                "host": "fake-host@lvm",
+                "replication_status": "disabled",
+                "active_backend_id": None,
+                "disabled_reason": None
+            }
+        ]
+    }
+
+    FAKE_SERVICE_REQUEST = {
+        "host": "fake-host",
+        "binary": "cinder-volume"
+    }
+
+    FAKE_SERVICE_RESPONSE = {
+        "disabled": False,
+        "status": "enabled",
+        "host": "fake-host@lvm",
+        "service": "",
+        "binary": "cinder-volume",
+        "disabled_reason": None
+    }
+
+    def setUp(self):
+        super(TestServicesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = services_client.ServicesClient(fake_auth,
+                                                     'volume',
+                                                     'regionOne')
+
+    def _test_list_services(self, bytes_body=False,
+                            mock_args='os-services', **params):
+        self.check_service_client_function(
+            self.client.list_services,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SERVICE_LIST,
+            to_utf=bytes_body,
+            mock_args=[mock_args],
+            **params)
+
+    def _test_enable_service(self, bytes_body=False):
+        resp_body = self.FAKE_SERVICE_RESPONSE
+        kwargs = self.FAKE_SERVICE_REQUEST
+        payload = json.dumps(kwargs, sort_keys=True)
+        json_dumps = json.dumps
+
+        # NOTE: Use sort_keys for json.dumps so that the expected and actual
+        # payloads are guaranteed to be identical for mock_args assert check.
+        with mock.patch.object(services_client.json, 'dumps') as mock_dumps:
+            mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+            self.check_service_client_function(
+                self.client.enable_service,
+                'tempest.lib.common.rest_client.RestClient.put',
+                resp_body,
+                to_utf=bytes_body,
+                mock_args=['os-services/enable', payload],
+                **kwargs)
+
+    def _test_disable_service(self, bytes_body=False):
+        resp_body = copy.deepcopy(self.FAKE_SERVICE_RESPONSE)
+        resp_body.pop('disabled_reason')
+        resp_body['disabled'] = True
+        resp_body['status'] = 'disabled'
+        kwargs = self.FAKE_SERVICE_REQUEST
+        payload = json.dumps(kwargs, sort_keys=True)
+        json_dumps = json.dumps
+
+        # NOTE: Use sort_keys for json.dumps so that the expected and actual
+        # payloads are guaranteed to be identical for mock_args assert check.
+        with mock.patch.object(services_client.json, 'dumps') as mock_dumps:
+            mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+            self.check_service_client_function(
+                self.client.disable_service,
+                'tempest.lib.common.rest_client.RestClient.put',
+                resp_body,
+                to_utf=bytes_body,
+                mock_args=['os-services/disable', payload],
+                **kwargs)
+
+    def _test_disable_log_reason(self, bytes_body=False):
+        resp_body = copy.deepcopy(self.FAKE_SERVICE_RESPONSE)
+        resp_body['disabled_reason'] = "disabled for test"
+        resp_body['disabled'] = True
+        resp_body['status'] = 'disabled'
+        kwargs = copy.deepcopy(self.FAKE_SERVICE_REQUEST)
+        kwargs.update({"disabled_reason": "disabled for test"})
+        payload = json.dumps(kwargs, sort_keys=True)
+        json_dumps = json.dumps
+
+        # NOTE: Use sort_keys for json.dumps so that the expected and actual
+        # payloads are guaranteed to be identical for mock_args assert check.
+        with mock.patch.object(services_client.json, 'dumps') as mock_dumps:
+            mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
+
+            self.check_service_client_function(
+                self.client.disable_log_reason,
+                'tempest.lib.common.rest_client.RestClient.put',
+                resp_body,
+                to_utf=bytes_body,
+                mock_args=['os-services/disable-log-reason', payload],
+                **kwargs)
+
+    def _test_freeze_host(self, bytes_body=False):
+        kwargs = {'host': 'host1@lvm'}
+        self.check_service_client_function(
+            self.client.freeze_host,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            **kwargs)
+
+    def _test_thaw_host(self, bytes_body=False):
+        kwargs = {'host': 'host1@lvm'}
+        self.check_service_client_function(
+            self.client.thaw_host,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            bytes_body,
+            **kwargs)
+
+    def test_list_services_with_str_body(self):
+        self._test_list_services()
+
+    def test_list_services_with_bytes_body(self):
+        self._test_list_services(bytes_body=True)
+
+    def test_list_services_with_params(self):
+        mock_args = 'os-services?host=fake-host'
+        self._test_list_services(mock_args=mock_args, host='fake-host')
+
+    def test_enable_service_with_str_body(self):
+        self._test_enable_service()
+
+    def test_enable_service_with_bytes_body(self):
+        self._test_enable_service(bytes_body=True)
+
+    def test_disable_service_with_str_body(self):
+        self._test_disable_service()
+
+    def test_disable_service_with_bytes_body(self):
+        self._test_disable_service(bytes_body=True)
+
+    def test_disable_log_reason_with_str_body(self):
+        self._test_disable_log_reason()
+
+    def test_disable_log_reason_with_bytes_body(self):
+        self._test_disable_log_reason(bytes_body=True)
+
+    def test_freeze_host_with_str_body(self):
+        self._test_freeze_host()
+
+    def test_freeze_host_with_bytes_body(self):
+        self._test_freeze_host(bytes_body=True)
+
+    def test_thaw_host_with_str_body(self):
+        self._test_thaw_host()
+
+    def test_thaw_host_with_bytes_body(self):
+        self._test_thaw_host(bytes_body=True)