Merge "Fix recent releasenotes typo"
diff --git a/releasenotes/notes/add-show-host-to-hosts-client-library-c60c4eb49d139480.yaml b/releasenotes/notes/add-show-host-to-hosts-client-library-c60c4eb49d139480.yaml
new file mode 100644
index 0000000..0de1803
--- /dev/null
+++ b/releasenotes/notes/add-show-host-to-hosts-client-library-c60c4eb49d139480.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add show host API to the volume v2 hosts_client library.
+    This feature enables the possibility to show details for a host.
diff --git a/releasenotes/notes/add-volume-backup-force-delete-af0156651a0cbf7f.yaml b/releasenotes/notes/add-volume-backup-force-delete-af0156651a0cbf7f.yaml
deleted file mode 100644
index 71bbfcb..0000000
--- a/releasenotes/notes/add-volume-backup-force-delete-af0156651a0cbf7f.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
----
-features:
-  - |
-    As in the [doc]:
-    https://developer.openstack.org/api-ref/block-storage/v3/
-    #force-delete-a-backup.
-
-      * Force-deletes a backup(v2)
-
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index db24174..83447b6 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -24,6 +24,11 @@
 class ImagesOneServerTestJSON(base.BaseV2ComputeTest):
 
     @classmethod
+    def resource_setup(cls):
+        super(ImagesOneServerTestJSON, cls).resource_setup()
+        cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
+
+    @classmethod
     def skip_checks(cls):
         super(ImagesOneServerTestJSON, cls).skip_checks()
         if not CONF.service_available.glance:
@@ -46,12 +51,10 @@
 
     @decorators.idempotent_id('3731d080-d4c5-4872-b41a-64d0d0021314')
     def test_create_delete_image(self):
-        server_id = self.create_test_server(wait_until='ACTIVE')['id']
-
         # Create a new image
         name = data_utils.rand_name('image')
         meta = {'image_type': 'test'}
-        image = self.create_image_from_server(server_id, name=name,
+        image = self.create_image_from_server(self.server_id, name=name,
                                               metadata=meta,
                                               wait_until='ACTIVE')
 
@@ -76,8 +79,6 @@
 
     @decorators.idempotent_id('3b7c6fe4-dfe7-477c-9243-b06359db51e6')
     def test_create_image_specify_multibyte_character_image_name(self):
-        server_id = self.create_test_server(wait_until='ACTIVE')['id']
-
         # prefix character is:
         # http://www.fileformat.info/info/unicode/char/1F4A9/index.htm
 
@@ -85,6 +86,6 @@
         # #1370954 in glance which will 500 if mysql is used as the
         # backend and it attempts to store a 4 byte utf-8 character
         utf8_name = data_utils.rand_name(b'\xe2\x82\xa1'.decode('utf-8'))
-        body = self.client.create_image(server_id, name=utf8_name)
+        body = self.client.create_image(self.server_id, name=utf8_name)
         image_id = data_utils.parse_image_id(body.response['location'])
         self.addCleanup(self.client.delete_image, image_id)
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index d89f44c..527f4bd 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -34,13 +34,12 @@
         # by the test methods in this class. These
         # servers are cleaned up automatically in the
         # tearDownClass method of the super-class.
-        cls.deleted_fixtures = []
-        cls.create_test_server(wait_until='ACTIVE', min_count=2)
+        body = cls.create_test_server(wait_until='ACTIVE', min_count=3)
 
-        srv = cls.create_test_server(wait_until='ACTIVE')
-        cls.client.delete_server(srv['id'])
-        waiters.wait_for_server_termination(cls.client, srv['id'])
-        cls.deleted_fixtures.append(srv)
+        # delete one of the created servers
+        cls.deleted_id = body['server']['id']
+        cls.client.delete_server(cls.deleted_id)
+        waiters.wait_for_server_termination(cls.client, cls.deleted_id)
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('24a26f1a-1ddc-4eea-b0d7-a90cc874ad8f')
@@ -49,9 +48,8 @@
         # List servers and verify server not returned
         body = self.client.list_servers()
         servers = body['servers']
-        deleted_ids = [s['id'] for s in self.deleted_fixtures]
         actual = [srv for srv in servers
-                  if srv['id'] in deleted_ids]
+                  if srv['id'] == self.deleted_id]
         self.assertEqual([], actual)
 
     @decorators.attr(type=['negative'])
@@ -136,9 +134,8 @@
     @decorators.idempotent_id('93055106-2d34-46fe-af68-d9ddbf7ee570')
     def test_list_servers_detail_server_is_deleted(self):
         # Server details are not listed for a deleted server
-        deleted_ids = [s['id'] for s in self.deleted_fixtures]
         body = self.client.list_servers(detail=True)
         servers = body['servers']
         actual = [srv for srv in servers
-                  if srv['id'] in deleted_ids]
+                  if srv['id'] == self.deleted_id]
         self.assertEqual([], actual)
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index 3b41775..42e13bd 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -35,16 +35,18 @@
             raise self.skipException('There are not any extensions configured')
         extensions = self.extensions_client.list_extensions()['extensions']
         ext = CONF.compute_feature_enabled.api_extensions[0]
-        if ext == 'all':
-            self.assertIn('Hosts', map(lambda x: x['name'], extensions))
-        elif ext:
-            self.assertIn(ext, map(lambda x: x['alias'], extensions))
-        else:
-            raise self.skipException('There are not any extensions configured')
+
         # Log extensions list
         extension_list = map(lambda x: x['alias'], extensions)
         LOG.debug("Nova extensions: %s", ','.join(extension_list))
 
+        if ext == 'all':
+            self.assertIn('Hosts', map(lambda x: x['name'], extensions))
+        elif ext:
+            self.assertIn(ext, extension_list)
+        else:
+            raise self.skipException('There are not any extensions configured')
+
     @decorators.idempotent_id('05762f39-bdfa-4cdb-9b46-b78f8e78e2fd')
     @test.requires_ext(extension='os-consoles', service='compute')
     def test_get_extension(self):
diff --git a/tempest/api/network/test_metering_extensions.py b/tempest/api/network/admin/test_metering_extensions.py
similarity index 100%
rename from tempest/api/network/test_metering_extensions.py
rename to tempest/api/network/admin/test_metering_extensions.py
diff --git a/tempest/api/network/admin/test_ports.py b/tempest/api/network/admin/test_ports.py
new file mode 100644
index 0000000..807994b
--- /dev/null
+++ b/tempest/api/network/admin/test_ports.py
@@ -0,0 +1,99 @@
+# Copyright 2014 OpenStack Foundation
+# 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 socket
+
+from tempest.api.network import base
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(PortsAdminExtendedAttrsTestJSON, cls).resource_setup()
+        cls.network = cls.create_network()
+        cls.host_id = socket.gethostname()
+
+    @decorators.idempotent_id('8e8569c1-9ac7-44db-8bc1-f5fb2814f29b')
+    def test_create_port_binding_ext_attr(self):
+        post_body = {"network_id": self.network['id'],
+                     "binding:host_id": self.host_id}
+        body = self.admin_ports_client.create_port(**post_body)
+        port = body['port']
+        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
+        host_id = port['binding:host_id']
+        self.assertIsNotNone(host_id)
+        self.assertEqual(self.host_id, host_id)
+
+    @decorators.idempotent_id('6f6c412c-711f-444d-8502-0ac30fbf5dd5')
+    def test_update_port_binding_ext_attr(self):
+        post_body = {"network_id": self.network['id']}
+        body = self.admin_ports_client.create_port(**post_body)
+        port = body['port']
+        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
+        update_body = {"binding:host_id": self.host_id}
+        body = self.admin_ports_client.update_port(port['id'], **update_body)
+        updated_port = body['port']
+        host_id = updated_port['binding:host_id']
+        self.assertIsNotNone(host_id)
+        self.assertEqual(self.host_id, host_id)
+
+    @decorators.idempotent_id('1c82a44a-6c6e-48ff-89e1-abe7eaf8f9f8')
+    def test_list_ports_binding_ext_attr(self):
+        # Create a new port
+        post_body = {"network_id": self.network['id']}
+        body = self.admin_ports_client.create_port(**post_body)
+        port = body['port']
+        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
+
+        # Update the port's binding attributes so that is now 'bound'
+        # to a host
+        update_body = {"binding:host_id": self.host_id}
+        self.admin_ports_client.update_port(port['id'], **update_body)
+
+        # List all ports, ensure new port is part of list and its binding
+        # attributes are set and accurate
+        body = self.admin_ports_client.list_ports()
+        ports_list = body['ports']
+        pids_list = [p['id'] for p in ports_list]
+        self.assertIn(port['id'], pids_list)
+        listed_port = [p for p in ports_list if p['id'] == port['id']]
+        self.assertEqual(1, len(listed_port),
+                         'Multiple ports listed with id %s in ports listing: '
+                         '%s' % (port['id'], ports_list))
+        self.assertEqual(self.host_id, listed_port[0]['binding:host_id'])
+
+    @decorators.idempotent_id('b54ac0ff-35fc-4c79-9ca3-c7dbd4ea4f13')
+    def test_show_port_binding_ext_attr(self):
+        body = self.admin_ports_client.create_port(
+            network_id=self.network['id'])
+        port = body['port']
+        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
+        body = self.admin_ports_client.show_port(port['id'])
+        show_port = body['port']
+        self.assertEqual(port['binding:host_id'],
+                         show_port['binding:host_id'])
+        self.assertEqual(port['binding:vif_type'],
+                         show_port['binding:vif_type'])
+        self.assertEqual(port['binding:vif_details'],
+                         show_port['binding:vif_details'])
+
+
+class PortsAdminExtendedAttrsIpV6TestJSON(PortsAdminExtendedAttrsTestJSON):
+    _ip_version = 6
diff --git a/tempest/api/network/base_routers.py b/tempest/api/network/base_routers.py
index f6fd871..6d4e756 100644
--- a/tempest/api/network/base_routers.py
+++ b/tempest/api/network/base_routers.py
@@ -33,15 +33,6 @@
         self.addCleanup(self._cleanup_router, router)
         return router
 
-    def _delete_router(self, router_id, routers_client=None):
-        client = routers_client or self.routers_client
-        client.delete_router(router_id)
-        # Asserting that the router is not found in the list
-        # after deletion
-        list_body = client.list_routers()
-        routers_list = [router['id'] for router in list_body['routers']]
-        self.assertNotIn(router_id, routers_list)
-
     def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
         interface = self.routers_client.add_router_interface(
             router_id, subnet_id=subnet_id)
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index 69a1441..f81927d 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -13,12 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import socket
-
 import netaddr
 import testtools
 
-from tempest.api.network import base
 from tempest.api.network import base_security_groups as sec_base
 from tempest.common import custom_matchers
 from tempest import config
@@ -358,82 +355,5 @@
         self.assertEmpty(port['security_groups'])
 
 
-class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest):
-
-    @classmethod
-    def resource_setup(cls):
-        super(PortsAdminExtendedAttrsTestJSON, cls).resource_setup()
-        cls.network = cls.create_network()
-        cls.host_id = socket.gethostname()
-
-    @decorators.idempotent_id('8e8569c1-9ac7-44db-8bc1-f5fb2814f29b')
-    def test_create_port_binding_ext_attr(self):
-        post_body = {"network_id": self.network['id'],
-                     "binding:host_id": self.host_id}
-        body = self.admin_ports_client.create_port(**post_body)
-        port = body['port']
-        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
-        host_id = port['binding:host_id']
-        self.assertIsNotNone(host_id)
-        self.assertEqual(self.host_id, host_id)
-
-    @decorators.idempotent_id('6f6c412c-711f-444d-8502-0ac30fbf5dd5')
-    def test_update_port_binding_ext_attr(self):
-        post_body = {"network_id": self.network['id']}
-        body = self.admin_ports_client.create_port(**post_body)
-        port = body['port']
-        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
-        update_body = {"binding:host_id": self.host_id}
-        body = self.admin_ports_client.update_port(port['id'], **update_body)
-        updated_port = body['port']
-        host_id = updated_port['binding:host_id']
-        self.assertIsNotNone(host_id)
-        self.assertEqual(self.host_id, host_id)
-
-    @decorators.idempotent_id('1c82a44a-6c6e-48ff-89e1-abe7eaf8f9f8')
-    def test_list_ports_binding_ext_attr(self):
-        # Create a new port
-        post_body = {"network_id": self.network['id']}
-        body = self.admin_ports_client.create_port(**post_body)
-        port = body['port']
-        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
-
-        # Update the port's binding attributes so that is now 'bound'
-        # to a host
-        update_body = {"binding:host_id": self.host_id}
-        self.admin_ports_client.update_port(port['id'], **update_body)
-
-        # List all ports, ensure new port is part of list and its binding
-        # attributes are set and accurate
-        body = self.admin_ports_client.list_ports()
-        ports_list = body['ports']
-        pids_list = [p['id'] for p in ports_list]
-        self.assertIn(port['id'], pids_list)
-        listed_port = [p for p in ports_list if p['id'] == port['id']]
-        self.assertEqual(1, len(listed_port),
-                         'Multiple ports listed with id %s in ports listing: '
-                         '%s' % (port['id'], ports_list))
-        self.assertEqual(self.host_id, listed_port[0]['binding:host_id'])
-
-    @decorators.idempotent_id('b54ac0ff-35fc-4c79-9ca3-c7dbd4ea4f13')
-    def test_show_port_binding_ext_attr(self):
-        body = self.admin_ports_client.create_port(
-            network_id=self.network['id'])
-        port = body['port']
-        self.addCleanup(self.admin_ports_client.delete_port, port['id'])
-        body = self.admin_ports_client.show_port(port['id'])
-        show_port = body['port']
-        self.assertEqual(port['binding:host_id'],
-                         show_port['binding:host_id'])
-        self.assertEqual(port['binding:vif_type'],
-                         show_port['binding:vif_type'])
-        self.assertEqual(port['binding:vif_details'],
-                         show_port['binding:vif_details'])
-
-
 class PortsIpV6TestJSON(PortsTestJSON):
     _ip_version = 6
-
-
-class PortsAdminExtendedAttrsIpV6TestJSON(PortsAdminExtendedAttrsTestJSON):
-    _ip_version = 6
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index 04d6cf9..e4ec442 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import random
+
 from tempest.api.volume import base
 from tempest.lib import decorators
 
@@ -22,5 +24,38 @@
     @decorators.idempotent_id('d5f3efa2-6684-4190-9ced-1c2f526352ad')
     def test_list_hosts(self):
         hosts = self.admin_hosts_client.list_hosts()['hosts']
-        self.assertGreaterEqual(len(hosts), 2, "No. of hosts are < 2,"
-                                "response of list hosts is: % s" % hosts)
+        self.assertGreaterEqual(len(hosts), 2,
+                                "The count of volume hosts is < 2, "
+                                "response of list hosts is: %s" % hosts)
+
+        # Check elements in volume hosts list
+        host_list_keys = ['service', 'host_name', 'last-update',
+                          'zone', 'service-status', 'service-state']
+        for host in hosts:
+            for key in host_list_keys:
+                self.assertIn(key, host)
+
+    @decorators.idempotent_id('21168d57-b373-4b71-a3ac-f2c88f0c5d31')
+    def test_show_host(self):
+        hosts = self.admin_hosts_client.list_hosts()['hosts']
+        self.assertGreaterEqual(len(hosts), 2,
+                                "The count of volume hosts is < 2, "
+                                "response of list hosts is: %s" % hosts)
+
+        # Note(jeremyZ): Host in volume is always presented in two formats:
+        # <host-name> or <host-name>@<driver-name>. Since Mitaka is EOL,
+        # both formats can be chosen for test.
+        host_names = [host['host_name'] for host in hosts]
+        self.assertNotEmpty(host_names, "No available volume host is found, "
+                                        "all hosts that found are: %s" % hosts)
+
+        # Choose a random host to get and check its elements
+        host_details = self.admin_hosts_client.show_host(
+            random.choice(host_names))['host']
+        self.assertNotEmpty(host_details)
+        host_detail_keys = ['project', 'volume_count', 'snapshot_count',
+                            'host', 'total_volume_gb', 'total_snapshot_gb']
+        for detail in host_details:
+            self.assertIn('resource', detail)
+            for key in host_detail_keys:
+                self.assertIn(key, detail['resource'])
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index a6f9246..afc3281 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -121,7 +121,7 @@
                                                 'available')
 
     @decorators.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94')
-    def test_volume_backup_reset_status_force_delete(self):
+    def test_volume_backup_reset_status(self):
         # Create a volume
         volume = self.create_volume()
         # Create a backup
@@ -136,6 +136,3 @@
                                                       status="error")
         waiters.wait_for_volume_resource_status(self.admin_backups_client,
                                                 backup['id'], 'error')
-        # Force delete a backup volume when backup is in error state.
-        self.admin_backups_client.force_delete_backup(backup['id'])
-        self.admin_backups_client.wait_for_resource_deletion(backup['id'])
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index c877969..cc1e087 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -144,8 +144,7 @@
                                                 snapshot['id'], 'available')
         return snapshot
 
-    def create_backup(self, volume_id, backup_client=None,
-                      wait_until="available", **kwargs):
+    def create_backup(self, volume_id, backup_client=None, **kwargs):
         """Wrapper utility that returns a test backup."""
         if backup_client is None:
             backup_client = self.backups_client
@@ -155,12 +154,9 @@
 
         backup = backup_client.create_backup(
             volume_id=volume_id, **kwargs)['backup']
-
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        backup_client.delete_backup, backup['id'])
-        waiters.wait_for_volume_resource_status(backup_client,
-                                                backup['id'],
-                                                wait_until)
+        self.addCleanup(backup_client.delete_backup, backup['id'])
+        waiters.wait_for_volume_resource_status(backup_client, backup['id'],
+                                                'available')
         return backup
 
     # NOTE(afazekas): these create_* and clean_* could be defined
diff --git a/tempest/lib/services/volume/v2/backups_client.py b/tempest/lib/services/volume/v2/backups_client.py
index 197d57e..2b5e82d 100644
--- a/tempest/lib/services/volume/v2/backups_client.py
+++ b/tempest/lib/services/volume/v2/backups_client.py
@@ -55,14 +55,6 @@
         self.expected_success(202, resp.status)
         return rest_client.ResponseBody(resp, body)
 
-    def force_delete_backup(self, backup_id):
-        """Force delete a backup volume."""
-        post_body = json.dumps({'os-force_delete': {}})
-        url = 'backups/%s/action' % backup_id
-        resp, body = self.post(url, post_body)
-        self.expected_success(202, resp.status)
-        return rest_client.ResponseBody(resp)
-
     def show_backup(self, backup_id):
         """Returns the details of a single backup."""
         url = "backups/%s" % backup_id
diff --git a/tempest/lib/services/volume/v2/hosts_client.py b/tempest/lib/services/volume/v2/hosts_client.py
index dd7c482..8fcf4d0 100644
--- a/tempest/lib/services/volume/v2/hosts_client.py
+++ b/tempest/lib/services/volume/v2/hosts_client.py
@@ -34,3 +34,11 @@
         body = json.loads(body)
         self.expected_success(200, resp.status)
         return rest_client.ResponseBody(resp, body)
+
+    def show_host(self, host_name):
+        """Show host details."""
+        url = 'os-hosts/%s' % host_name
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/tests/lib/services/volume/v2/test_hosts_client.py b/tempest/tests/lib/services/volume/v2/test_hosts_client.py
new file mode 100644
index 0000000..e107910
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_hosts_client.py
@@ -0,0 +1,97 @@
+# Copyright 2017 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.lib.services.volume.v2 import hosts_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQuotasClient(base.BaseServiceTest):
+    FAKE_LIST_HOSTS = {
+        "hosts": [
+            {
+                "service-status": "available",
+                "service": "cinder-scheduler",
+                "zone": "nova",
+                "service-state": "enabled",
+                "host_name": "fake-host",
+                "last-update": "2017-04-12T04:26:03.000000"
+            },
+            {
+                "service-status": "available",
+                "service": "cinder-volume",
+                "zone": "nova",
+                "service-state": "enabled",
+                "host_name": "fake-host@rbd",
+                "last-update": "2017-04-12T04:26:07.000000"
+            }
+        ]
+    }
+
+    FAKE_HOST_INFO = {
+        "host": [
+            {
+                "resource": {
+                    "volume_count": "2",
+                    "total_volume_gb": "2",
+                    "total_snapshot_gb": "0",
+                    "project": "(total)",
+                    "host": "fake-host",
+                    "snapshot_count": "0"
+                }
+            },
+            {
+                "resource": {
+                    "volume_count": "2",
+                    "total_volume_gb": "2",
+                    "total_snapshot_gb": "0",
+                    "project": "f21a9c86d7114bf99c711f4874d80474",
+                    "host": "fake-host",
+                    "snapshot_count": "0"
+                }
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestQuotasClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = hosts_client.HostsClient(fake_auth,
+                                               'volume',
+                                               'regionOne')
+
+    def _test_list_hosts(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_hosts,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_HOSTS, bytes_body)
+
+    def _test_show_host(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_host,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_HOST_INFO, bytes_body, host_name='fake-host')
+
+    def test_list_hosts_with_str_body(self):
+        self._test_list_hosts()
+
+    def test_list_hosts_with_bytes_body(self):
+        self._test_list_hosts(bytes_body=True)
+
+    def test_show_host_with_str_body(self):
+        self._test_show_host()
+
+    def test_show_host_with_bytes_body(self):
+        self._test_show_host(bytes_body=True)