Merge "Remove unused wait_for function"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 607ba8b..e7145e7 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -234,6 +234,9 @@
 # indicates every extension is enabled (list value)
 #api_extensions=all
 
+# Is the v1 volume API enabled (boolean value)
+#api_v1=true
+
 
 [image-feature-enabled]
 
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 73d274c..1e5b6d2 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -270,6 +270,8 @@
         cls.volumes_client = cls.os.volumes_client
         cls.certificates_client = cls.os.certificates_v3_client
         cls.keypairs_client = cls.os.keypairs_v3_client
+        cls.aggregates_client = cls.os.aggregates_v3_client
+        cls.hosts_client = cls.os.hosts_v3_client
 
     @classmethod
     def create_image_from_server(cls, server_id, **kwargs):
@@ -328,10 +330,12 @@
             os_adm = clients.ComputeAdminManager(interface=cls._interface)
 
         cls.os_adm = os_adm
-        cls.severs_admin_client = cls.os_adm.servers_v3_client
+        cls.servers_admin_client = cls.os_adm.servers_v3_client
         cls.services_admin_client = cls.os_adm.services_v3_client
         cls.availability_zone_admin_client = \
             cls.os_adm.availability_zone_v3_client
         cls.hypervisor_admin_client = cls.os_adm.hypervisor_v3_client
         cls.tenant_usages_admin_client = cls.os_adm.tenant_usages_v3_client
         cls.flavors_admin_client = cls.os_adm.flavors_v3_client
+        cls.aggregates_admin_client = cls.os_adm.aggregates_v3_client
+        cls.hosts_admin_client = cls.os_adm.hosts_v3_client
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 618abe2..76e0cae 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -17,7 +17,6 @@
 
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
-from tempest import exceptions
 from tempest.test import attr
 
 
@@ -110,49 +109,6 @@
         expected = {'key2': 'value2'}
         self.assertEqual(expected, resp_metadata)
 
-    @attr(type=['negative', 'gate'])
-    def test_list_nonexistant_image_metadata(self):
-        # Negative test: List on nonexistant image
-        # metadata should not happen
-        self.assertRaises(exceptions.NotFound, self.client.list_image_metadata,
-                          999)
-
-    @attr(type=['negative', 'gate'])
-    def test_update_nonexistant_image_metadata(self):
-        # Negative test:An update should not happen for a non-existent image
-        meta = {'key1': 'alt1', 'key2': 'alt2'}
-        self.assertRaises(exceptions.NotFound,
-                          self.client.update_image_metadata, 999, meta)
-
-    @attr(type=['negative', 'gate'])
-    def test_get_nonexistant_image_metadata_item(self):
-        # Negative test: Get on non-existent image should not happen
-        self.assertRaises(exceptions.NotFound,
-                          self.client.get_image_metadata_item, 999, 'key2')
-
-    @attr(type=['negative', 'gate'])
-    def test_set_nonexistant_image_metadata(self):
-        # Negative test: Metadata should not be set to a non-existent image
-        meta = {'key1': 'alt1', 'key2': 'alt2'}
-        self.assertRaises(exceptions.NotFound, self.client.set_image_metadata,
-                          999, meta)
-
-    @attr(type=['negative', 'gate'])
-    def test_set_nonexistant_image_metadata_item(self):
-        # Negative test: Metadata item should not be set to a
-        # nonexistant image
-        meta = {'key1': 'alt'}
-        self.assertRaises(exceptions.NotFound,
-                          self.client.set_image_metadata_item, 999, 'key1',
-                          meta)
-
-    @attr(type=['negative', 'gate'])
-    def test_delete_nonexistant_image_metadata_item(self):
-        # Negative test: Shouldn't be able to delete metadata
-        # item from non-existent image
-        self.assertRaises(exceptions.NotFound,
-                          self.client.delete_image_metadata_item, 999, 'key1')
-
 
 class ImagesMetadataTestXML(ImagesMetadataTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
new file mode 100644
index 0000000..1767e5d
--- /dev/null
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -0,0 +1,81 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(ImagesMetadataTestJSON, cls).setUpClass()
+        cls.client = cls.images_client
+
+    @attr(type=['negative', 'gate'])
+    def test_list_nonexistent_image_metadata(self):
+        # Negative test: List on nonexistent image
+        # metadata should not happen
+        self.assertRaises(exceptions.NotFound, self.client.list_image_metadata,
+                          data_utils.rand_uuid())
+
+    @attr(type=['negative', 'gate'])
+    def test_update_nonexistent_image_metadata(self):
+        # Negative test:An update should not happen for a non-existent image
+        meta = {'key1': 'alt1', 'key2': 'alt2'}
+        self.assertRaises(exceptions.NotFound,
+                          self.client.update_image_metadata,
+                          data_utils.rand_uuid(), meta)
+
+    @attr(type=['negative', 'gate'])
+    def test_get_nonexistent_image_metadata_item(self):
+        # Negative test: Get on non-existent image should not happen
+        self.assertRaises(exceptions.NotFound,
+                          self.client.get_image_metadata_item,
+                          data_utils.rand_uuid(), 'key2')
+
+    @attr(type=['negative', 'gate'])
+    def test_set_nonexistent_image_metadata(self):
+        # Negative test: Metadata should not be set to a non-existent image
+        meta = {'key1': 'alt1', 'key2': 'alt2'}
+        self.assertRaises(exceptions.NotFound, self.client.set_image_metadata,
+                          data_utils.rand_uuid(), meta)
+
+    @attr(type=['negative', 'gate'])
+    def test_set_nonexistent_image_metadata_item(self):
+        # Negative test: Metadata item should not be set to a
+        # nonexistent image
+        meta = {'key1': 'alt'}
+        self.assertRaises(exceptions.NotFound,
+                          self.client.set_image_metadata_item,
+                          data_utils.rand_uuid(), 'key1',
+                          meta)
+
+    @attr(type=['negative', 'gate'])
+    def test_delete_nonexistent_image_metadata_item(self):
+        # Negative test: Shouldn't be able to delete metadata
+        # item from non-existent image
+        self.assertRaises(exceptions.NotFound,
+                          self.client.delete_image_metadata_item,
+                          data_utils.rand_uuid(), 'key1')
+
+
+class ImagesMetadataTestXML(ImagesMetadataTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_aggregates.py b/tempest/api/compute/v3/admin/test_aggregates.py
new file mode 100644
index 0000000..144dc44
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_aggregates.py
@@ -0,0 +1,221 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation.
+# 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.compute import base
+from tempest.common import tempest_fixtures as fixtures
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class AggregatesAdminV3TestJSON(base.BaseV3ComputeAdminTest):
+
+    """
+    Tests Aggregates API that require admin privileges
+    """
+
+    _host_key = 'os-extended-server-attributes:host'
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(AggregatesAdminV3TestJSON, cls).setUpClass()
+        cls.client = cls.aggregates_admin_client
+        cls.user_client = cls.aggregates_client
+        cls.aggregate_name_prefix = 'test_aggregate_'
+        cls.az_name_prefix = 'test_az_'
+
+        resp, hosts_all = cls.hosts_admin_client.list_hosts()
+        hosts = map(lambda x: x['host_name'],
+                    filter(lambda y: y['service'] == 'compute', hosts_all))
+        cls.host = hosts[0]
+
+    @test.attr(type='gate')
+    def test_aggregate_create_delete(self):
+        # Create and delete an aggregate.
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.assertEqual(201, resp.status)
+        self.assertEqual(aggregate_name, aggregate['name'])
+        self.assertEqual(None, aggregate['availability_zone'])
+
+        resp, _ = self.client.delete_aggregate(aggregate['id'])
+        self.assertEqual(204, resp.status)
+        self.client.wait_for_resource_deletion(aggregate['id'])
+
+    @test.attr(type='gate')
+    def test_aggregate_create_delete_with_az(self):
+        # Create and delete an aggregate.
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        az_name = data_utils.rand_name(self.az_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name, az_name)
+        self.assertEqual(201, resp.status)
+        self.assertEqual(aggregate_name, aggregate['name'])
+        self.assertEqual(az_name, aggregate['availability_zone'])
+
+        resp, _ = self.client.delete_aggregate(aggregate['id'])
+        self.assertEqual(204, resp.status)
+        self.client.wait_for_resource_deletion(aggregate['id'])
+
+    @test.attr(type='gate')
+    def test_aggregate_create_verify_entry_in_list(self):
+        # Create an aggregate and ensure it is listed.
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        resp, aggregates = self.client.list_aggregates()
+        self.assertEqual(200, resp.status)
+        self.assertIn((aggregate['id'], aggregate['availability_zone']),
+                      map(lambda x: (x['id'], x['availability_zone']),
+                          aggregates))
+
+    @test.attr(type='gate')
+    def test_aggregate_create_update_metadata_get_details(self):
+        # Create an aggregate and ensure its details are returned.
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        resp, body = self.client.get_aggregate(aggregate['id'])
+        self.assertEqual(200, resp.status)
+        self.assertEqual(aggregate['name'], body['name'])
+        self.assertEqual(aggregate['availability_zone'],
+                         body['availability_zone'])
+        self.assertEqual({}, body["metadata"])
+
+        # set the metadata of the aggregate
+        meta = {"key": "value"}
+        resp, body = self.client.set_metadata(aggregate['id'], meta)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(meta, body["metadata"])
+
+        # verify the metadata has been set
+        resp, body = self.client.get_aggregate(aggregate['id'])
+        self.assertEqual(200, resp.status)
+        self.assertEqual(meta, body["metadata"])
+
+    @test.attr(type='gate')
+    def test_aggregate_create_update_with_az(self):
+        # Update an aggregate and ensure properties are updated correctly
+        self.useFixture(fixtures.LockFixture('availability_zone'))
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        az_name = data_utils.rand_name(self.az_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name, az_name)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        self.assertEqual(201, resp.status)
+        self.assertEqual(aggregate_name, aggregate['name'])
+        self.assertEqual(az_name, aggregate['availability_zone'])
+        self.assertIsNotNone(aggregate['id'])
+
+        aggregate_id = aggregate['id']
+        new_aggregate_name = aggregate_name + '_new'
+        new_az_name = az_name + '_new'
+
+        resp, resp_aggregate = self.client.update_aggregate(aggregate_id,
+                                                            new_aggregate_name,
+                                                            new_az_name)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(new_aggregate_name, resp_aggregate['name'])
+        self.assertEqual(new_az_name, resp_aggregate['availability_zone'])
+
+        resp, aggregates = self.client.list_aggregates()
+        self.assertEqual(200, resp.status)
+        self.assertIn((aggregate_id, new_aggregate_name, new_az_name),
+                      map(lambda x:
+                         (x['id'], x['name'], x['availability_zone']),
+                          aggregates))
+
+    @test.attr(type='gate')
+    def test_aggregate_add_remove_host(self):
+        # Add an host to the given aggregate and remove.
+        self.useFixture(fixtures.LockFixture('availability_zone'))
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        resp, body = self.client.add_host(aggregate['id'], self.host)
+        self.assertEqual(202, resp.status)
+        self.assertEqual(aggregate_name, body['name'])
+        self.assertEqual(aggregate['availability_zone'],
+                         body['availability_zone'])
+        self.assertIn(self.host, body['hosts'])
+
+        resp, body = self.client.remove_host(aggregate['id'], self.host)
+        self.assertEqual(202, resp.status)
+        self.assertEqual(aggregate_name, body['name'])
+        self.assertEqual(aggregate['availability_zone'],
+                         body['availability_zone'])
+        self.assertNotIn(self.host, body['hosts'])
+
+    @test.attr(type='gate')
+    def test_aggregate_add_host_list(self):
+        # Add an host to the given aggregate and list.
+        self.useFixture(fixtures.LockFixture('availability_zone'))
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+        self.client.add_host(aggregate['id'], self.host)
+        self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+        resp, aggregates = self.client.list_aggregates()
+        aggs = filter(lambda x: x['id'] == aggregate['id'], aggregates)
+        self.assertEqual(1, len(aggs))
+        agg = aggs[0]
+        self.assertEqual(aggregate_name, agg['name'])
+        self.assertEqual(None, agg['availability_zone'])
+        self.assertIn(self.host, agg['hosts'])
+
+    @test.attr(type='gate')
+    def test_aggregate_add_host_get_details(self):
+        # Add an host to the given aggregate and get details.
+        self.useFixture(fixtures.LockFixture('availability_zone'))
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+        self.client.add_host(aggregate['id'], self.host)
+        self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+        resp, body = self.client.get_aggregate(aggregate['id'])
+        self.assertEqual(aggregate_name, body['name'])
+        self.assertEqual(None, body['availability_zone'])
+        self.assertIn(self.host, body['hosts'])
+
+    @test.attr(type='gate')
+    def test_aggregate_add_host_create_server_with_az(self):
+        # Add an host to the given aggregate and create a server.
+        self.useFixture(fixtures.LockFixture('availability_zone'))
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        az_name = data_utils.rand_name(self.az_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name, az_name)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+        self.client.add_host(aggregate['id'], self.host)
+        self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+        server_name = data_utils.rand_name('test_server_')
+        admin_servers_client = self.servers_admin_client
+        resp, server = self.create_test_server(name=server_name,
+                                               availability_zone=az_name,
+                                               wait_until='ACTIVE')
+        resp, body = admin_servers_client.get_server(server['id'])
+        self.assertEqual(self.host, body[self._host_key])
+
+
+class AggregatesAdminV3TestXML(AggregatesAdminV3TestJSON):
+    _host_key = (
+        '{http://docs.openstack.org/compute/ext/'
+        'extended_server_attributes/api/v3}host')
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_aggregates_negative.py b/tempest/api/compute/v3/admin/test_aggregates_negative.py
new file mode 100644
index 0000000..87eadce
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_aggregates_negative.py
@@ -0,0 +1,196 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei 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.compute import base
+from tempest.common import tempest_fixtures as fixtures
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+
+class AggregatesAdminNegativeV3TestJSON(base.BaseV3ComputeAdminTest):
+
+    """
+    Tests Aggregates API that require admin privileges
+    """
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(AggregatesAdminNegativeV3TestJSON, cls).setUpClass()
+        cls.client = cls.aggregates_admin_client
+        cls.user_client = cls.aggregates_client
+        cls.aggregate_name_prefix = 'test_aggregate_'
+        cls.az_name_prefix = 'test_az_'
+
+        resp, hosts_all = cls.hosts_admin_client.list_hosts()
+        hosts = map(lambda x: x['host_name'],
+                    filter(lambda y: y['service'] == 'compute', hosts_all))
+        cls.host = hosts[0]
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_create_as_user(self):
+        # Regular user is not allowed to create an aggregate.
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        self.assertRaises(exceptions.Unauthorized,
+                          self.user_client.create_aggregate,
+                          aggregate_name)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_create_aggregate_name_length_less_than_1(self):
+        # the length of aggregate name should >= 1 and <=255
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.create_aggregate,
+                          '')
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_create_aggregate_name_length_exceeds_255(self):
+        # the length of aggregate name should >= 1 and <=255
+        aggregate_name = 'a' * 256
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.create_aggregate,
+                          aggregate_name)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_create_with_existent_aggregate_name(self):
+        # creating an aggregate with existent aggregate name is forbidden
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.assertEqual(201, resp.status)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        self.assertRaises(exceptions.Conflict,
+                          self.client.create_aggregate,
+                          aggregate_name)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_delete_as_user(self):
+        # Regular user is not allowed to delete an aggregate.
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.assertEqual(201, resp.status)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.user_client.delete_aggregate,
+                          aggregate['id'])
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_list_as_user(self):
+        # Regular user is not allowed to list aggregates.
+        self.assertRaises(exceptions.Unauthorized,
+                          self.user_client.list_aggregates)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_get_details_as_user(self):
+        # Regular user is not allowed to get aggregate details.
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.assertEqual(201, resp.status)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.user_client.get_aggregate,
+                          aggregate['id'])
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_delete_with_invalid_id(self):
+        # Delete an aggregate with invalid id should raise exceptions.
+        self.assertRaises(exceptions.NotFound,
+                          self.client.delete_aggregate, -1)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_get_details_with_invalid_id(self):
+        # Get aggregate details with invalid id should raise exceptions.
+        self.assertRaises(exceptions.NotFound,
+                          self.client.get_aggregate, -1)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_add_non_exist_host(self):
+        # Adding a non-exist host to an aggregate should raise exceptions.
+        resp, hosts_all = self.hosts_admin_client.list_hosts()
+        hosts = map(lambda x: x['host_name'], hosts_all)
+        while True:
+            non_exist_host = data_utils.rand_name('nonexist_host_')
+            if non_exist_host not in hosts:
+                break
+
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        self.assertRaises(exceptions.NotFound, self.client.add_host,
+                          aggregate['id'], non_exist_host)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_add_host_as_user(self):
+        # Regular user is not allowed to add a host to an aggregate.
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.assertEqual(201, resp.status)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.user_client.add_host,
+                          aggregate['id'], self.host)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_add_existent_host(self):
+        self.useFixture(fixtures.LockFixture('availability_zone'))
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.assertEqual(201, resp.status)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        resp, body = self.client.add_host(aggregate['id'], self.host)
+        self.assertEqual(202, resp.status)
+        self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+        self.assertRaises(exceptions.Conflict, self.client.add_host,
+                          aggregate['id'], self.host)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_remove_host_as_user(self):
+        # Regular user is not allowed to remove a host from an aggregate.
+        self.useFixture(fixtures.LockFixture('availability_zone'))
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.assertEqual(201, resp.status)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+        resp, body = self.client.add_host(aggregate['id'], self.host)
+        self.assertEqual(202, resp.status)
+        self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.user_client.remove_host,
+                          aggregate['id'], self.host)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_aggregate_remove_nonexistent_host(self):
+        non_exist_host = data_utils.rand_name('nonexist_host_')
+        aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+        resp, aggregate = self.client.create_aggregate(aggregate_name)
+        self.assertEqual(201, resp.status)
+        self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+        self.assertRaises(exceptions.NotFound, self.client.remove_host,
+                          aggregate['id'], non_exist_host)
+
+
+class AggregatesAdminNegativeV3TestXML(AggregatesAdminNegativeV3TestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_hosts.py b/tempest/api/compute/v3/admin/test_hosts.py
new file mode 100644
index 0000000..896d6a7
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_hosts.py
@@ -0,0 +1,94 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+#    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.compute import base
+from tempest.common import tempest_fixtures as fixtures
+from tempest import test
+
+
+class HostsAdminV3TestJSON(base.BaseV3ComputeAdminTest):
+
+    """
+    Tests hosts API using admin privileges.
+    """
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(HostsAdminV3TestJSON, cls).setUpClass()
+        cls.client = cls.hosts_admin_client
+
+    @test.attr(type='gate')
+    def test_list_hosts(self):
+        resp, hosts = self.client.list_hosts()
+        self.assertEqual(200, resp.status)
+        self.assertTrue(len(hosts) >= 2, str(hosts))
+
+    @test.attr(type='gate')
+    def test_list_hosts_with_zone(self):
+        self.useFixture(fixtures.LockFixture('availability_zone'))
+        resp, hosts = self.client.list_hosts()
+        host = hosts[0]
+        zone_name = host['zone']
+        params = {'zone': zone_name}
+        resp, hosts = self.client.list_hosts(params)
+        self.assertEqual(200, resp.status)
+        self.assertTrue(len(hosts) >= 1)
+        self.assertIn(host, hosts)
+
+    @test.attr(type='gate')
+    def test_list_hosts_with_a_blank_zone(self):
+        # If send the request with a blank zone, the request will be successful
+        # and it will return all the hosts list
+        params = {'zone': ''}
+        resp, hosts = self.client.list_hosts(params)
+        self.assertNotEqual(0, len(hosts))
+        self.assertEqual(200, resp.status)
+
+    @test.attr(type='gate')
+    def test_list_hosts_with_nonexistent_zone(self):
+        # If send the request with a nonexistent zone, the request will be
+        # successful and no hosts will be retured
+        params = {'zone': 'xxx'}
+        resp, hosts = self.client.list_hosts(params)
+        self.assertEqual(0, len(hosts))
+        self.assertEqual(200, resp.status)
+
+    @test.attr(type='gate')
+    def test_show_host_detail(self):
+        resp, hosts = self.client.list_hosts()
+        self.assertEqual(200, resp.status)
+
+        hosts = [host for host in hosts if host['service'] == 'compute']
+        self.assertTrue(len(hosts) >= 1)
+
+        for host in hosts:
+            hostname = host['host_name']
+            resp, resources = self.client.show_host_detail(hostname)
+            self.assertEqual(200, resp.status)
+            self.assertTrue(len(resources) >= 1)
+            host_resource = resources[0]['resource']
+            self.assertIsNotNone(host_resource)
+            self.assertIsNotNone(host_resource['cpu'])
+            self.assertIsNotNone(host_resource['disk_gb'])
+            self.assertIsNotNone(host_resource['memory_mb'])
+            self.assertIsNotNone(host_resource['project'])
+            self.assertEqual(hostname, host_resource['host'])
+
+
+class HostsAdminV3TestXML(HostsAdminV3TestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_hosts_negative.py b/tempest/api/compute/v3/admin/test_hosts_negative.py
new file mode 100644
index 0000000..755fa2b
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_hosts_negative.py
@@ -0,0 +1,174 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD.
+#
+#    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.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+
+class HostsAdminNegativeV3TestJSON(base.BaseV3ComputeAdminTest):
+
+    """
+    Tests hosts API using admin privileges.
+    """
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(HostsAdminNegativeV3TestJSON, cls).setUpClass()
+        cls.client = cls.hosts_admin_client
+        cls.non_admin_client = cls.hosts_client
+
+    def _get_host_name(self):
+        resp, hosts = self.client.list_hosts()
+        self.assertEqual(200, resp.status)
+        self.assertTrue(len(hosts) >= 1)
+        hostname = hosts[0]['host_name']
+        return hostname
+
+    @test.attr(type=['negative', 'gate'])
+    def test_list_hosts_with_non_admin_user(self):
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.list_hosts)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_show_host_detail_with_nonexistent_hostname(self):
+        nonexitent_hostname = data_utils.rand_name('rand_hostname')
+        self.assertRaises(exceptions.NotFound,
+                          self.client.show_host_detail, nonexitent_hostname)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_show_host_detail_with_non_admin_user(self):
+        hostname = self._get_host_name()
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.show_host_detail,
+                          hostname)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_update_host_with_non_admin_user(self):
+        hostname = self._get_host_name()
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.update_host,
+                          hostname)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_update_host_with_extra_param(self):
+        # only 'status' and 'maintenance_mode' are the valid params.
+        hostname = self._get_host_name()
+
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_host,
+                          hostname,
+                          status='enable',
+                          maintenance_mode='enable',
+                          param='XXX')
+
+    @test.attr(type=['negative', 'gate'])
+    def test_update_host_with_invalid_status(self):
+        # 'status' can only be 'enable' or 'disable'
+        hostname = self._get_host_name()
+
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_host,
+                          hostname,
+                          status='invalid',
+                          maintenance_mode='enable')
+
+    @test.attr(type=['negative', 'gate'])
+    def test_update_host_with_invalid_maintenance_mode(self):
+        # 'maintenance_mode' can only be 'enable' or 'disable'
+        hostname = self._get_host_name()
+
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_host,
+                          hostname,
+                          status='enable',
+                          maintenance_mode='invalid')
+
+    @test.attr(type=['negative', 'gate'])
+    def test_update_host_without_param(self):
+        # 'status' or 'maintenance_mode' needed for host update
+        hostname = self._get_host_name()
+
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_host,
+                          hostname)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_update_nonexistent_host(self):
+        nonexitent_hostname = data_utils.rand_name('rand_hostname')
+
+        self.assertRaises(exceptions.NotFound,
+                          self.client.update_host,
+                          nonexitent_hostname,
+                          status='enable',
+                          maintenance_mode='enable')
+
+    @test.attr(type=['negative', 'gate'])
+    def test_startup_nonexistent_host(self):
+        nonexitent_hostname = data_utils.rand_name('rand_hostname')
+
+        self.assertRaises(exceptions.NotFound,
+                          self.client.startup_host,
+                          nonexitent_hostname)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_startup_host_with_non_admin_user(self):
+        hostname = self._get_host_name()
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.startup_host,
+                          hostname)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_shutdown_nonexistent_host(self):
+        nonexitent_hostname = data_utils.rand_name('rand_hostname')
+
+        self.assertRaises(exceptions.NotFound,
+                          self.client.shutdown_host,
+                          nonexitent_hostname)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_shutdown_host_with_non_admin_user(self):
+        hostname = self._get_host_name()
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.shutdown_host,
+                          hostname)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_reboot_nonexistent_host(self):
+        nonexitent_hostname = data_utils.rand_name('rand_hostname')
+
+        self.assertRaises(exceptions.NotFound,
+                          self.client.reboot_host,
+                          nonexitent_hostname)
+
+    @test.attr(type=['negative', 'gate'])
+    def test_reboot_host_with_non_admin_user(self):
+        hostname = self._get_host_name()
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.reboot_host,
+                          hostname)
+
+
+class HostsAdminNegativeV3TestXML(HostsAdminNegativeV3TestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/v3/test_live_block_migration.py b/tempest/api/compute/v3/test_live_block_migration.py
new file mode 100644
index 0000000..3de50c8
--- /dev/null
+++ b/tempest/api/compute/v3/test_live_block_migration.py
@@ -0,0 +1,173 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 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 random
+import string
+
+import testtools
+
+from tempest.api.compute import base
+from tempest import config
+from tempest import exceptions
+from tempest.test import attr
+
+
+class LiveBlockMigrationV3TestJSON(base.BaseV3ComputeAdminTest):
+    _host_key = 'os-extended-server-attributes:host'
+    _interface = 'json'
+
+    CONF = config.CONF
+
+    @classmethod
+    def setUpClass(cls):
+        super(LiveBlockMigrationV3TestJSON, cls).setUpClass()
+
+        cls.admin_hosts_client = cls.hosts_admin_client
+        cls.admin_servers_client = cls.servers_admin_client
+
+        cls.created_server_ids = []
+
+    def _get_compute_hostnames(self):
+        _resp, body = self.admin_hosts_client.list_hosts()
+        return [
+            host_record['host_name']
+            for host_record in body
+            if host_record['service'] == 'compute'
+        ]
+
+    def _get_server_details(self, server_id):
+        _resp, body = self.admin_servers_client.get_server(server_id)
+        return body
+
+    def _get_host_for_server(self, server_id):
+        return self._get_server_details(server_id)[self._host_key]
+
+    def _migrate_server_to(self, server_id, dest_host):
+        _resp, body = self.admin_servers_client.live_migrate_server(
+            server_id, dest_host,
+            self.config.compute_feature_enabled.
+            block_migration_for_live_migration)
+        return body
+
+    def _get_host_other_than(self, host):
+        for target_host in self._get_compute_hostnames():
+            if host != target_host:
+                return target_host
+
+    def _get_non_existing_host_name(self):
+        random_name = ''.join(
+            random.choice(string.ascii_uppercase) for x in range(20))
+
+        self.assertNotIn(random_name, self._get_compute_hostnames())
+
+        return random_name
+
+    def _get_server_status(self, server_id):
+        return self._get_server_details(server_id)['status']
+
+    def _get_an_active_server(self):
+        for server_id in self.created_server_ids:
+            if 'ACTIVE' == self._get_server_status(server_id):
+                return server_id
+        else:
+            _, server = self.create_test_server(wait_until="ACTIVE")
+            server_id = server['id']
+            self.password = server['admin_password']
+            self.password = 'password'
+            self.created_server_ids.append(server_id)
+            return server_id
+
+    def _volume_clean_up(self, server_id, volume_id):
+        resp, body = self.volumes_client.get_volume(volume_id)
+        if body['status'] == 'in-use':
+            self.servers_client.detach_volume(server_id, volume_id)
+            self.volumes_client.wait_for_volume_status(volume_id, 'available')
+        self.volumes_client.delete_volume(volume_id)
+
+    @testtools.skipIf(not CONF.compute_feature_enabled.live_migration,
+                      'Live migration not available')
+    @attr(type='gate')
+    def test_live_block_migration(self):
+        # Live block migrate an instance to another host
+        if len(self._get_compute_hostnames()) < 2:
+            raise self.skipTest(
+                "Less than 2 compute nodes, skipping migration test.")
+        server_id = self._get_an_active_server()
+        actual_host = self._get_host_for_server(server_id)
+        target_host = self._get_host_other_than(actual_host)
+        self._migrate_server_to(server_id, target_host)
+        self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+        self.assertEqual(target_host, self._get_host_for_server(server_id))
+
+    @testtools.skipIf(not CONF.compute_feature_enabled.live_migration,
+                      'Live migration not available')
+    @attr(type='gate')
+    def test_invalid_host_for_migration(self):
+        # Migrating to an invalid host should not change the status
+        server_id = self._get_an_active_server()
+        target_host = self._get_non_existing_host_name()
+
+        self.assertRaises(exceptions.BadRequest, self._migrate_server_to,
+                          server_id, target_host)
+        self.assertEqual('ACTIVE', self._get_server_status(server_id))
+
+    @testtools.skipIf(not CONF.compute_feature_enabled.live_migration or not
+                      CONF.compute_feature_enabled.
+                      block_migration_for_live_migration,
+                      'Block Live migration not available')
+    @testtools.skipIf(not CONF.compute_feature_enabled.
+                      block_migrate_cinder_iscsi,
+                      'Block Live migration not configured for iSCSI')
+    @attr(type='gate')
+    def test_iscsi_volume(self):
+        # Live block migrate an instance to another host
+        if len(self._get_compute_hostnames()) < 2:
+            raise self.skipTest(
+                "Less than 2 compute nodes, skipping migration test.")
+        server_id = self._get_an_active_server()
+        actual_host = self._get_host_for_server(server_id)
+        target_host = self._get_host_other_than(actual_host)
+
+        resp, volume = self.volumes_client.create_volume(1,
+                                                         display_name='test')
+
+        self.volumes_client.wait_for_volume_status(volume['id'],
+                                                   'available')
+        self.addCleanup(self._volume_clean_up, server_id, volume['id'])
+
+        # Attach the volume to the server
+        self.servers_client.attach_volume(server_id, volume['id'],
+                                          device='/dev/xvdb')
+        self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
+
+        self._migrate_server_to(server_id, target_host)
+        self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+        self.assertEqual(target_host, self._get_host_for_server(server_id))
+
+    @classmethod
+    def tearDownClass(cls):
+        for server_id in cls.created_server_ids:
+            cls.servers_client.delete_server(server_id)
+
+        super(LiveBlockMigrationV3TestJSON, cls).tearDownClass()
+
+
+class LiveBlockMigrationV3TestXML(LiveBlockMigrationV3TestJSON):
+    _host_key = (
+        '{http://docs.openstack.org/compute/ext/'
+        'extended_server_attributes/api/v3}host')
+    _interface = 'xml'
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 064eaff..318d891 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -169,13 +169,23 @@
     @classmethod
     def create_pool(cls, name, lb_method, protocol, subnet):
         """Wrapper utility that returns a test pool."""
-        resp, body = cls.client.create_pool(name, lb_method, protocol,
-                                            subnet['id'])
+        resp, body = cls.client.create_pool(
+            name=name,
+            lb_method=lb_method,
+            protocol=protocol,
+            subnet_id=subnet['id'])
         pool = body['pool']
         cls.pools.append(pool)
         return pool
 
     @classmethod
+    def update_pool(cls, name):
+        """Wrapper utility that returns a test pool."""
+        resp, body = cls.client.update_pool(name=name)
+        pool = body['pool']
+        return pool
+
+    @classmethod
     def create_vip(cls, name, protocol, protocol_port, subnet, pool):
         """Wrapper utility that returns a test vip."""
         resp, body = cls.client.create_vip(name, protocol, protocol_port,
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index 7e4ec37..224c36c 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -69,9 +69,11 @@
     def test_create_update_delete_pool_vip(self):
         # Creates a vip
         name = data_utils.rand_name('vip-')
-        resp, body = self.client.create_pool(data_utils.rand_name("pool-"),
-                                             "ROUND_ROBIN", "HTTP",
-                                             self.subnet['id'])
+        resp, body = self.client.create_pool(
+            name=data_utils.rand_name("pool-"),
+            lb_method='ROUND_ROBIN',
+            protocol='HTTP',
+            subnet_id=self.subnet['id'])
         pool = body['pool']
         resp, body = self.client.create_vip(name, "HTTP", 80,
                                             self.subnet['id'], pool['id'])
@@ -89,7 +91,8 @@
         self.assertEqual('204', resp['status'])
         # Verification of pool update
         new_name = "New_pool"
-        resp, body = self.client.update_pool(pool['id'], new_name)
+        resp, body = self.client.update_pool(pool['id'],
+                                             name=new_name)
         self.assertEqual('200', resp['status'])
         updated_pool = body['pool']
         self.assertEqual(updated_pool['name'], new_name)
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 03e8469..c563259 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -22,7 +22,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class VolumeMultiBackendTest(base.BaseVolumeAdminTest):
+class VolumeMultiBackendTest(base.BaseVolumeV1AdminTest):
     _interface = "json"
 
     @classmethod
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index 5e838e5..03fbd33 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -15,12 +15,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.volume.base import BaseVolumeAdminTest
+from tempest.api.volume import base
 from tempest.common.utils import data_utils
 from tempest.test import attr
 
 
-class SnapshotsActionsTest(BaseVolumeAdminTest):
+class SnapshotsActionsTest(base.BaseVolumeV1AdminTest):
     _interface = "json"
 
     @classmethod
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
index e7d8c02..4f40d4a 100644
--- a/tempest/api/volume/admin/test_volume_hosts.py
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -19,7 +19,7 @@
 from tempest.test import attr
 
 
-class VolumeHostsAdminTestsJSON(base.BaseVolumeAdminTest):
+class VolumeHostsAdminTestsJSON(base.BaseVolumeV1AdminTest):
     _interface = "json"
 
     @attr(type='gate')
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 5218f83..3a92e8d 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -15,13 +15,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.volume.base import BaseVolumeTest
+from tempest.api.volume import base
 from tempest.common.utils import data_utils
 from tempest.services.volume.json.admin import volume_types_client
 from tempest.test import attr
 
 
-class VolumeTypesTest(BaseVolumeTest):
+class VolumeTypesTest(base.BaseVolumeV1Test):
     _interface = "json"
 
     @classmethod
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index dbb75af..f0fba07 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -20,7 +20,7 @@
 from tempest.test import attr
 
 
-class VolumeTypesExtraSpecsTest(base.BaseVolumeAdminTest):
+class VolumeTypesExtraSpecsTest(base.BaseVolumeV1AdminTest):
     _interface = "json"
 
     @classmethod
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index 8b5dce2..cf992f2 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -23,7 +23,7 @@
 from tempest.test import attr
 
 
-class ExtraSpecsNegativeTest(base.BaseVolumeAdminTest):
+class ExtraSpecsNegativeTest(base.BaseVolumeV1AdminTest):
     _interface = 'json'
 
     @classmethod
diff --git a/tempest/api/volume/admin/test_volume_types_negative.py b/tempest/api/volume/admin/test_volume_types_negative.py
index 44725df..3832048 100644
--- a/tempest/api/volume/admin/test_volume_types_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_negative.py
@@ -22,7 +22,7 @@
 from tempest.test import attr
 
 
-class VolumeTypesNegativeTest(base.BaseVolumeAdminTest):
+class VolumeTypesNegativeTest(base.BaseVolumeV1AdminTest):
     _interface = 'json'
 
     @attr(type='gate')
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index cb9ff11..941dc7e 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -15,12 +15,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.volume.base import BaseVolumeAdminTest
+from tempest.api.volume import base
 from tempest.common.utils import data_utils as utils
 from tempest.test import attr
 
 
-class VolumesActionsTest(BaseVolumeAdminTest):
+class VolumesActionsTest(base.BaseVolumeV1AdminTest):
     _interface = "json"
 
     @classmethod
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index af307ea..643d021 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -34,13 +34,12 @@
             skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
 
-        os = cls.get_client_manager()
+        cls.os = cls.get_client_manager()
 
-        cls.os = os
-        cls.volumes_client = os.volumes_client
-        cls.snapshots_client = os.snapshots_client
-        cls.servers_client = os.servers_client
-        cls.volumes_extension_client = os.volumes_extension_client
+        cls.volumes_client = cls.os.volumes_client
+        cls.snapshots_client = cls.os.snapshots_client
+        cls.servers_client = cls.os.servers_client
+        cls.volumes_extension_client = cls.os.volumes_extension_client
         cls.image_ref = cls.config.compute.image_ref
         cls.flavor_ref = cls.config.compute.flavor_ref
         cls.build_interval = cls.config.volume.build_interval
@@ -48,12 +47,6 @@
         cls.snapshots = []
         cls.volumes = []
 
-        cls.volumes_client.keystone_auth(cls.os.username,
-                                         cls.os.password,
-                                         cls.os.auth_url,
-                                         cls.volumes_client.service,
-                                         cls.os.tenant_name)
-
     @classmethod
     def tearDownClass(cls):
         cls.clear_snapshots()
@@ -113,11 +106,26 @@
                 pass
 
 
-class BaseVolumeAdminTest(BaseVolumeTest):
+class BaseVolumeV1Test(BaseVolumeTest):
+    @classmethod
+    def setUpClass(cls):
+        if not cls.config.volume_feature_enabled.api_v1:
+            msg = "Volume API v1 not supported"
+            raise cls.skipException(msg)
+        super(BaseVolumeV1Test, cls).setUpClass()
+        cls.volumes_client = cls.os.volumes_client
+        cls.volumes_client.keystone_auth(cls.os.username,
+                                         cls.os.password,
+                                         cls.os.auth_url,
+                                         cls.volumes_client.service,
+                                         cls.os.tenant_name)
+
+
+class BaseVolumeV1AdminTest(BaseVolumeV1Test):
     """Base test case class for all Volume Admin API tests."""
     @classmethod
     def setUpClass(cls):
-        super(BaseVolumeAdminTest, cls).setUpClass()
+        super(BaseVolumeV1AdminTest, cls).setUpClass()
         cls.adm_user = cls.config.identity.admin_username
         cls.adm_pass = cls.config.identity.admin_password
         cls.adm_tenant = cls.config.identity.admin_tenant_name
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index dacebf1..71e9f85 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -15,13 +15,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.volume.base import BaseVolumeTest
+from tempest.api.volume import base
 from tempest import clients
 from tempest.common.utils.data_utils import rand_name
 from tempest.test import attr
 
 
-class VolumesTransfersTest(BaseVolumeTest):
+class VolumesTransfersTest(base.BaseVolumeV1Test):
     _interface = "json"
 
     @classmethod
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 8581d16..61f1bda 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -15,14 +15,14 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.volume.base import BaseVolumeTest
+from tempest.api.volume import base
 from tempest.common.utils import data_utils
 from tempest.test import attr
 from tempest.test import services
 from tempest.test import stresstest
 
 
-class VolumesActionsTest(BaseVolumeTest):
+class VolumesActionsTest(base.BaseVolumeV1Test):
     _interface = "json"
 
     @classmethod
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 14120fe..6d1c25a 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -21,7 +21,7 @@
 from tempest.test import services
 
 
-class VolumesGetTest(base.BaseVolumeTest):
+class VolumesGetTest(base.BaseVolumeV1Test):
     _interface = "json"
 
     @classmethod
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 3c66eb8..c624a3a 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -27,7 +27,7 @@
 VOLUME_FIELDS = ('id', 'display_name')
 
 
-class VolumesListTest(base.BaseVolumeTest):
+class VolumesListTest(base.BaseVolumeV1Test):
 
     """
     This test creates a number of 1G volumes. To run successfully,
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 928bd49..869aedb 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -23,7 +23,7 @@
 from tempest.test import attr
 
 
-class VolumesNegativeTest(base.BaseVolumeTest):
+class VolumesNegativeTest(base.BaseVolumeV1Test):
     _interface = 'json'
 
     @classmethod
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 6c45c3d..4e57007 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -20,7 +20,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class VolumesSnapshotTest(base.BaseVolumeTest):
+class VolumesSnapshotTest(base.BaseVolumeV1Test):
     _interface = "json"
 
     @classmethod
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index 04a4774..0e4f5dc 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -20,7 +20,7 @@
 from tempest.test import attr
 
 
-class VolumesSnapshotNegativeTest(base.BaseVolumeTest):
+class VolumesSnapshotNegativeTest(base.BaseVolumeV1Test):
     _interface = "json"
 
     @attr(type=['negative', 'gate'])
diff --git a/tempest/cli/simple_read_only/test_ceilometer.py b/tempest/cli/simple_read_only/test_ceilometer.py
index 7f2864f..8bdd633 100644
--- a/tempest/cli/simple_read_only/test_ceilometer.py
+++ b/tempest/cli/simple_read_only/test_ceilometer.py
@@ -49,3 +49,6 @@
 
     def test_ceilometermeter_alarm_list(self):
         self.ceilometer('alarm-list')
+
+    def test_ceilometer_version(self):
+        self.ceilometer('', flags='--version')
diff --git a/tempest/clients.py b/tempest/clients.py
index 1f2e1de..f4548d5 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -50,6 +50,8 @@
     TenantUsagesClientJSON
 from tempest.services.compute.json.volumes_extensions_client import \
     VolumesExtensionsClientJSON
+from tempest.services.compute.v3.json.aggregates_client import \
+    AggregatesV3ClientJSON
 from tempest.services.compute.v3.json.availability_zone_client import \
     AvailabilityZoneV3ClientJSON
 from tempest.services.compute.v3.json.certificates_client import \
@@ -57,6 +59,7 @@
 from tempest.services.compute.v3.json.extensions_client import \
     ExtensionsV3ClientJSON
 from tempest.services.compute.v3.json.flavors_client import FlavorsV3ClientJSON
+from tempest.services.compute.v3.json.hosts_client import HostsV3ClientJSON
 from tempest.services.compute.v3.json.hypervisor_client import \
     HypervisorV3ClientJSON
 from tempest.services.compute.v3.json.interfaces_client import \
@@ -69,6 +72,8 @@
     ServicesV3ClientJSON
 from tempest.services.compute.v3.json.tenant_usages_client import \
     TenantUsagesV3ClientJSON
+from tempest.services.compute.v3.xml.aggregates_client import \
+    AggregatesV3ClientXML
 from tempest.services.compute.v3.xml.availability_zone_client import \
     AvailabilityZoneV3ClientXML
 from tempest.services.compute.v3.xml.certificates_client import \
@@ -76,6 +81,7 @@
 from tempest.services.compute.v3.xml.extensions_client import \
     ExtensionsV3ClientXML
 from tempest.services.compute.v3.xml.flavors_client import FlavorsV3ClientXML
+from tempest.services.compute.v3.xml.hosts_client import HostsV3ClientXML
 from tempest.services.compute.v3.xml.hypervisor_client import \
     HypervisorV3ClientXML
 from tempest.services.compute.v3.xml.interfaces_client import \
@@ -254,6 +260,7 @@
                 *client_args)
             self.services_v3_client = ServicesV3ClientXML(*client_args)
             self.service_client = ServiceClientXML(*client_args)
+            self.aggregates_v3_client = AggregatesV3ClientXML(*client_args)
             self.aggregates_client = AggregatesClientXML(*client_args)
             self.services_client = ServicesClientXML(*client_args)
             self.tenant_usages_v3_client = TenantUsagesV3ClientXML(
@@ -271,6 +278,7 @@
             self.volume_hosts_client = VolumeHostsClientXML(*client_args)
             self.volumes_extension_client = VolumeExtensionClientXML(
                 *client_args)
+            self.hosts_v3_client = HostsV3ClientXML(*client_args)
 
             if client_args_v3_auth:
                 self.servers_client_v3_auth = ServersClientXML(
@@ -312,6 +320,7 @@
                 *client_args)
             self.services_v3_client = ServicesV3ClientJSON(*client_args)
             self.service_client = ServiceClientJSON(*client_args)
+            self.aggregates_v3_client = AggregatesV3ClientJSON(*client_args)
             self.aggregates_client = AggregatesClientJSON(*client_args)
             self.services_client = ServicesClientJSON(*client_args)
             self.tenant_usages_v3_client = TenantUsagesV3ClientJSON(
@@ -329,6 +338,7 @@
             self.volume_hosts_client = VolumeHostsClientJSON(*client_args)
             self.volumes_extension_client = VolumeExtensionClientJSON(
                 *client_args)
+            self.hosts_v3_client = HostsV3ClientJSON(*client_args)
 
             if client_args_v3_auth:
                 self.servers_client_v3_auth = ServersClientJSON(
diff --git a/tempest/config.py b/tempest/config.py
index 1247a8d..bf45b4b 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -377,6 +377,9 @@
                 default=['all'],
                 help='A list of enabled extensions with a special entry all '
                      'which indicates every extension is enabled'),
+    cfg.BoolOpt('api_v1',
+                default=True,
+                help="Is the v1 volume API enabled"),
 ]
 
 
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index d3d34d0..409fcc2 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -985,7 +985,7 @@
         username = cls.config.identity.admin_username
         password = cls.config.identity.admin_password
         tenant_name = cls.config.identity.tenant_name
-        return username, tenant_name, password
+        return username, password, tenant_name
 
     def _load_template(self, base_file, file_name):
         filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
diff --git a/tempest/services/compute/v3/json/aggregates_client.py b/tempest/services/compute/v3/json/aggregates_client.py
new file mode 100644
index 0000000..8ca2770
--- /dev/null
+++ b/tempest/services/compute/v3/json/aggregates_client.py
@@ -0,0 +1,111 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation.
+# 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 json
+
+from tempest.common.rest_client import RestClient
+from tempest import exceptions
+
+
+class AggregatesV3ClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(AggregatesV3ClientJSON, self).__init__(config, username,
+                                                     password, auth_url,
+                                                     tenant_name)
+        self.service = self.config.compute.catalog_v3_type
+
+    def list_aggregates(self):
+        """Get aggregate list."""
+        resp, body = self.get("os-aggregates")
+        body = json.loads(body)
+        return resp, body['aggregates']
+
+    def get_aggregate(self, aggregate_id):
+        """Get details of the given aggregate."""
+        resp, body = self.get("os-aggregates/%s" % str(aggregate_id))
+        body = json.loads(body)
+        return resp, body['aggregate']
+
+    def create_aggregate(self, name, availability_zone=None):
+        """Creates a new aggregate."""
+        post_body = {
+            'name': name,
+            'availability_zone': availability_zone,
+        }
+        post_body = json.dumps({'aggregate': post_body})
+        resp, body = self.post('os-aggregates', post_body, self.headers)
+
+        body = json.loads(body)
+        return resp, body['aggregate']
+
+    def update_aggregate(self, aggregate_id, name, availability_zone=None):
+        """Update a aggregate."""
+        put_body = {
+            'name': name,
+            'availability_zone': availability_zone
+        }
+        put_body = json.dumps({'aggregate': put_body})
+        resp, body = self.put('os-aggregates/%s' % str(aggregate_id),
+                              put_body, self.headers)
+
+        body = json.loads(body)
+        return resp, body['aggregate']
+
+    def delete_aggregate(self, aggregate_id):
+        """Deletes the given aggregate."""
+        return self.delete("os-aggregates/%s" % str(aggregate_id))
+
+    def is_resource_deleted(self, id):
+        try:
+            self.get_aggregate(id)
+        except exceptions.NotFound:
+            return True
+        return False
+
+    def add_host(self, aggregate_id, host):
+        """Adds a host to the given aggregate."""
+        post_body = {
+            'host': host,
+        }
+        post_body = json.dumps({'add_host': post_body})
+        resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+                               post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['aggregate']
+
+    def remove_host(self, aggregate_id, host):
+        """Removes a host from the given aggregate."""
+        post_body = {
+            'host': host,
+        }
+        post_body = json.dumps({'remove_host': post_body})
+        resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+                               post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['aggregate']
+
+    def set_metadata(self, aggregate_id, meta):
+        """Replaces the aggregate's existing metadata with new metadata."""
+        post_body = {
+            'metadata': meta,
+        }
+        post_body = json.dumps({'set_metadata': post_body})
+        resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+                               post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['aggregate']
diff --git a/tempest/services/compute/v3/json/hosts_client.py b/tempest/services/compute/v3/json/hosts_client.py
new file mode 100644
index 0000000..85cc34f
--- /dev/null
+++ b/tempest/services/compute/v3/json/hosts_client.py
@@ -0,0 +1,82 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 IBM Corp.
+#
+#    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 json
+import urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class HostsV3ClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(HostsV3ClientJSON, self).__init__(config, username, password,
+                                                auth_url, tenant_name)
+        self.service = self.config.compute.catalog_v3_type
+
+    def list_hosts(self, params=None):
+        """Lists all hosts."""
+
+        url = 'os-hosts'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body['hosts']
+
+    def show_host_detail(self, hostname):
+        """Show detail information for the host."""
+
+        resp, body = self.get("os-hosts/%s" % str(hostname))
+        body = json.loads(body)
+        return resp, body['host']
+
+    def update_host(self, hostname, **kwargs):
+        """Update a host."""
+
+        request_body = {
+            'status': None,
+            'maintenance_mode': None,
+        }
+        request_body.update(**kwargs)
+        request_body = json.dumps({'host': request_body})
+
+        resp, body = self.put("os-hosts/%s" % str(hostname), request_body,
+                              self.headers)
+        body = json.loads(body)
+        return resp, body
+
+    def startup_host(self, hostname):
+        """Startup a host."""
+
+        resp, body = self.get("os-hosts/%s/startup" % str(hostname))
+        body = json.loads(body)
+        return resp, body['host']
+
+    def shutdown_host(self, hostname):
+        """Shutdown a host."""
+
+        resp, body = self.get("os-hosts/%s/shutdown" % str(hostname))
+        body = json.loads(body)
+        return resp, body['host']
+
+    def reboot_host(self, hostname):
+        """reboot a host."""
+
+        resp, body = self.get("os-hosts/%s/reboot" % str(hostname))
+        body = json.loads(body)
+        return resp, body['host']
diff --git a/tempest/services/compute/v3/xml/aggregates_client.py b/tempest/services/compute/v3/xml/aggregates_client.py
new file mode 100644
index 0000000..7563fd6
--- /dev/null
+++ b/tempest/services/compute/v3/xml/aggregates_client.py
@@ -0,0 +1,130 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation.
+# 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 lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest import exceptions
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class AggregatesV3ClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(AggregatesV3ClientXML, self).__init__(config, username, password,
+                                                    auth_url, tenant_name)
+        self.service = self.config.compute.catalog_v3_type
+
+    def _format_aggregate(self, g):
+        agg = xml_to_json(g)
+        aggregate = {}
+        for key, value in agg.items():
+            if key == 'hosts':
+                aggregate['hosts'] = []
+                for k, v in value.items():
+                    aggregate['hosts'].append(v)
+            elif key == 'availability_zone':
+                aggregate[key] = None if value == 'None' else value
+            else:
+                aggregate[key] = value
+        return aggregate
+
+    def _parse_array(self, node):
+        return [self._format_aggregate(x) for x in node]
+
+    def list_aggregates(self):
+        """Get aggregate list."""
+        resp, body = self.get("os-aggregates", self.headers)
+        aggregates = self._parse_array(etree.fromstring(body))
+        return resp, aggregates
+
+    def get_aggregate(self, aggregate_id):
+        """Get details of the given aggregate."""
+        resp, body = self.get("os-aggregates/%s" % str(aggregate_id),
+                              self.headers)
+        aggregate = self._format_aggregate(etree.fromstring(body))
+        return resp, aggregate
+
+    def create_aggregate(self, name, availability_zone=None):
+        """Creates a new aggregate."""
+        post_body = Element("aggregate",
+                            name=name,
+                            availability_zone=availability_zone)
+        resp, body = self.post('os-aggregates',
+                               str(Document(post_body)),
+                               self.headers)
+        aggregate = self._format_aggregate(etree.fromstring(body))
+        return resp, aggregate
+
+    def update_aggregate(self, aggregate_id, name, availability_zone=None):
+        """Update a aggregate."""
+        put_body = Element("aggregate",
+                           name=name,
+                           availability_zone=availability_zone)
+        resp, body = self.put('os-aggregates/%s' % str(aggregate_id),
+                              str(Document(put_body)),
+                              self.headers)
+        aggregate = self._format_aggregate(etree.fromstring(body))
+        return resp, aggregate
+
+    def delete_aggregate(self, aggregate_id):
+        """Deletes the given aggregate."""
+        return self.delete("os-aggregates/%s" % str(aggregate_id),
+                           self.headers)
+
+    def is_resource_deleted(self, id):
+        try:
+            self.get_aggregate(id)
+        except exceptions.NotFound:
+            return True
+        return False
+
+    def add_host(self, aggregate_id, host):
+        """Adds a host to the given aggregate."""
+        post_body = Element("add_host", host=host)
+        resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+                               str(Document(post_body)),
+                               self.headers)
+        aggregate = self._format_aggregate(etree.fromstring(body))
+        return resp, aggregate
+
+    def remove_host(self, aggregate_id, host):
+        """Removes a host from the given aggregate."""
+        post_body = Element("remove_host", host=host)
+        resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+                               str(Document(post_body)),
+                               self.headers)
+        aggregate = self._format_aggregate(etree.fromstring(body))
+        return resp, aggregate
+
+    def set_metadata(self, aggregate_id, meta):
+        """Replaces the aggregate's existing metadata with new metadata."""
+        post_body = Element("set_metadata")
+        metadata = Element("metadata")
+        post_body.append(metadata)
+        for k, v in meta.items():
+            meta = Element(k)
+            meta.append(Text(v))
+            metadata.append(meta)
+        resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
+                               str(Document(post_body)),
+                               self.headers)
+        aggregate = self._format_aggregate(etree.fromstring(body))
+        return resp, aggregate
diff --git a/tempest/services/compute/v3/xml/hosts_client.py b/tempest/services/compute/v3/xml/hosts_client.py
new file mode 100644
index 0000000..82fb076
--- /dev/null
+++ b/tempest/services/compute/v3/xml/hosts_client.py
@@ -0,0 +1,92 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 IBM Corp.
+#
+#    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 urllib
+
+from lxml import etree
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class HostsV3ClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(HostsV3ClientXML, self).__init__(config, username, password,
+                                               auth_url, tenant_name)
+        self.service = self.config.compute.catalog_v3_type
+
+    def list_hosts(self, params=None):
+        """Lists all hosts."""
+
+        url = 'os-hosts'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url, self.headers)
+        node = etree.fromstring(body)
+        body = [xml_to_json(x) for x in node.getchildren()]
+        return resp, body
+
+    def show_host_detail(self, hostname):
+        """Show detail information for the host."""
+
+        resp, body = self.get("os-hosts/%s" % str(hostname), self.headers)
+        node = etree.fromstring(body)
+        body = [xml_to_json(node)]
+        return resp, body
+
+    def update_host(self, hostname, **kwargs):
+        """Update a host."""
+
+        request_body = Element("host")
+        if kwargs:
+            for k, v in kwargs.iteritems():
+                request_body.append(Element(k, v))
+        resp, body = self.put("os-hosts/%s" % str(hostname),
+                              str(Document(request_body)),
+                              self.headers)
+        node = etree.fromstring(body)
+        body = [xml_to_json(x) for x in node.getchildren()]
+        return resp, body
+
+    def startup_host(self, hostname):
+        """Startup a host."""
+
+        resp, body = self.get("os-hosts/%s/startup" % str(hostname),
+                              self.headers)
+        node = etree.fromstring(body)
+        body = [xml_to_json(x) for x in node.getchildren()]
+        return resp, body
+
+    def shutdown_host(self, hostname):
+        """Shutdown a host."""
+
+        resp, body = self.get("os-hosts/%s/shutdown" % str(hostname),
+                              self.headers)
+        node = etree.fromstring(body)
+        body = [xml_to_json(x) for x in node.getchildren()]
+        return resp, body
+
+    def reboot_host(self, hostname):
+        """Reboot a host."""
+
+        resp, body = self.get("os-hosts/%s/reboot" % str(hostname),
+                              self.headers)
+        node = etree.fromstring(body)
+        body = [xml_to_json(x) for x in node.getchildren()]
+        return resp, body
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index b323dc6..e26697e 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -46,6 +46,9 @@
         # {'resources': [ res1, res2] }
         return res[res.keys()[0]]
 
+    def serialize(self, data):
+        return json.dumps(data)
+
     def create_network(self, name, **kwargs):
         post_body = {'network': kwargs}
         post_body['network']['name'] = name
@@ -303,21 +306,6 @@
         body = json.loads(body)
         return resp, body
 
-    def create_pool(self, name, lb_method, protocol, subnet_id):
-        post_body = {
-            "pool": {
-                "protocol": protocol,
-                "name": name,
-                "subnet_id": subnet_id,
-                "lb_method": lb_method
-            }
-        }
-        body = json.dumps(post_body)
-        uri = '%s/lb/pools' % (self.uri_prefix)
-        resp, body = self.post(uri, body)
-        body = json.loads(body)
-        return resp, body
-
     def update_vip(self, vip_id, new_name):
         put_body = {
             "vip": {
@@ -330,18 +318,6 @@
         body = json.loads(body)
         return resp, body
 
-    def update_pool(self, pool_id, new_name):
-        put_body = {
-            "pool": {
-                "name": new_name,
-            }
-        }
-        body = json.dumps(put_body)
-        uri = '%s/lb/pools/%s' % (self.uri_prefix, pool_id)
-        resp, body = self.put(uri, body)
-        body = json.loads(body)
-        return resp, body
-
     def create_member(self, address, protocol_port, pool_id):
         post_body = {
             "member": {
diff --git a/tempest/services/network/network_client_base.py b/tempest/services/network/network_client_base.py
index 45ea2d6..42ca5bf 100644
--- a/tempest/services/network/network_client_base.py
+++ b/tempest/services/network/network_client_base.py
@@ -124,11 +124,35 @@
 
         return _show
 
+    def _creater(self, resource_name):
+        def _create(**kwargs):
+            plural = self.pluralize(resource_name)
+            uri = self.get_uri(plural)
+            post_data = self.serialize({resource_name: kwargs})
+            resp, body = self.post(uri, post_data)
+            body = self.deserialize_single(body)
+            return resp, body
+
+        return _create
+
+    def _updater(self, resource_name):
+        def _update(res_id, **kwargs):
+            plural = self.pluralize(resource_name)
+            uri = '%s/%s' % (self.get_uri(plural), res_id)
+            post_data = self.serialize({resource_name: kwargs})
+            resp, body = self.put(uri, post_data)
+            body = self.deserialize_single(body)
+            return resp, body
+
+        return _update
+
     def __getattr__(self, name):
-        method_prefixes = ["list_", "delete_", "show_"]
+        method_prefixes = ["list_", "delete_", "show_", "create_", "update_"]
         method_functors = [self._lister,
                            self._deleter,
-                           self._shower]
+                           self._shower,
+                           self._creater,
+                           self._updater]
         for index, prefix in enumerate(method_prefixes):
             prefix_len = len(prefix)
             if name[:prefix_len] == prefix:
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index 155fa35..a57f278 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -41,6 +41,16 @@
     def deserialize_single(self, body):
         return _root_tag_fetcher_and_xml_to_json_parse(body)
 
+    def serialize(self, body):
+        #TODO(enikanorov): implement better json to xml conversion
+        # expecting the dict with single key
+        root = body.keys()[0]
+        post_body = Element(root)
+        for name, attr in body[root].items():
+            elt = Element(name, attr)
+            post_body.append(elt)
+        return str(Document(post_body))
+
     def create_network(self, name):
         uri = '%s/networks' % (self.uri_prefix)
         post_body = Element("network")
@@ -195,28 +205,6 @@
         body = _root_tag_fetcher_and_xml_to_json_parse(body)
         return resp, body
 
-    def create_pool(self, name, lb_method, protocol, subnet_id):
-        uri = '%s/lb/pools' % (self.uri_prefix)
-        post_body = Element("pool")
-        p1 = Element("lb_method", lb_method)
-        p2 = Element("protocol", protocol)
-        p3 = Element("subnet_id", subnet_id)
-        post_body.append(p1)
-        post_body.append(p2)
-        post_body.append(p3)
-        resp, body = self.post(uri, str(Document(post_body)))
-        body = _root_tag_fetcher_and_xml_to_json_parse(body)
-        return resp, body
-
-    def update_pool(self, pool_id, new_name):
-        uri = '%s/lb/pools/%s' % (self.uri_prefix, str(pool_id))
-        put_body = Element("pool")
-        p2 = Element("name", new_name)
-        put_body.append(p2)
-        resp, body = self.put(uri, str(Document(put_body)))
-        body = _root_tag_fetcher_and_xml_to_json_parse(body)
-        return resp, body
-
     def create_member(self, address, protocol_port, pool_id):
         uri = '%s/lb/members' % (self.uri_prefix)
         post_body = Element("member")