Merge "Fix import group ordering in test_utils.py"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 2fdbb7e..a184c76 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -101,14 +101,32 @@
 # Options defined in tempest.config
 #
 
-# Catalog type of the baremetal provisioning service. (string
+# Catalog type of the baremetal provisioning service (string
 # value)
 #catalog_type=baremetal
 
+# Whether the Ironic nova-compute driver is enabled (boolean
+# value)
+#driver_enabled=false
+
 # The endpoint type to use for the baremetal provisioning
-# service. (string value)
+# service (string value)
 #endpoint_type=publicURL
 
+# Timeout for Ironic node to completely provision (integer
+# value)
+#active_timeout=300
+
+# Timeout for association of Nova instance and Ironic node
+# (integer value)
+#association_timeout=10
+
+# Timeout for Ironic power transitions. (integer value)
+#power_timeout=20
+
+# Timeout for unprovisioning an Ironic node. (integer value)
+#unprovision_timeout=20
+
 
 [boto]
 
@@ -193,7 +211,7 @@
 # admin credentials are known. (boolean value)
 #allow_tenant_isolation=false
 
-# Valid secondary image reference to be used in tests. (string
+# Valid primary image reference to be used in tests. (string
 # value)
 #image_ref={$IMAGE_ID}
 
@@ -960,6 +978,10 @@
 # value)
 #disk_format=raw
 
+# Default size in GB for volumes created by volumes tests
+# (integer value)
+#volume_size=1
+
 
 [volume-feature-enabled]
 
diff --git a/requirements.txt b/requirements.txt
index 3521df0..e97eece 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-pbr>=0.6,<1.0
+pbr>=0.6,!=0.7,<1.0
 anyjson>=0.3.3
 httplib2>=0.7.5
 jsonschema>=2.0.0,<3.0.0
@@ -13,10 +13,11 @@
 python-neutronclient>=2.3.4,<3
 python-cinderclient>=1.0.6
 python-heatclient>=0.2.3
+python-ironicclient
 python-saharaclient>=0.6.0
 python-swiftclient>=1.6
 testresources>=0.2.4
-keyring>=1.6.1,<2.0,>=2.1
+keyring>=2.1
 testrepository>=0.0.18
 oslo.config>=1.2.0
 six>=1.5.2
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index 7f909d7..c87f24e 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -60,7 +60,7 @@
         resp, cls.server = cls.client.get_server(server['id'])
 
         name = data_utils.rand_name('image')
-        resp, body = cls.client.create_image(server['id'], name)
+        resp, body = cls.images_client.create_image(server['id'], name)
         image_id = data_utils.parse_image_id(resp['location'])
         cls.images_client.wait_for_image_status(image_id, 'ACTIVE')
         resp, cls.image = cls.images_client.get_image(image_id)
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index 24c7b83..90dccca 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -73,7 +73,7 @@
         self.assertIn(role_id, fetched_role_ids)
 
     @test.attr(type='smoke')
-    def test_role_create_update_get(self):
+    def test_role_create_update_get_list(self):
         r_name = data_utils.rand_name('Role-')
         resp, role = self.client.create_role(r_name)
         self.addCleanup(self.client.delete_role, role['id'])
@@ -94,6 +94,10 @@
         self.assertEqual(new_name, new_role['name'])
         self.assertEqual(updated_role['id'], new_role['id'])
 
+        resp, roles = self.client.list_roles()
+        self.assertEqual(resp['status'], '200')
+        self.assertIn(role['id'], [r['id'] for r in roles])
+
     @test.attr(type='smoke')
     def test_grant_list_revoke_role_to_user_on_project(self):
         resp, _ = self.client.assign_user_role_on_project(
diff --git a/tempest/api/network/admin/test_agent_management.py b/tempest/api/network/admin/test_agent_management.py
index 342bc6a..b848994 100644
--- a/tempest/api/network/admin/test_agent_management.py
+++ b/tempest/api/network/admin/test_agent_management.py
@@ -37,8 +37,10 @@
         agents = body['agents']
         # Hearthbeats must be excluded from comparison
         self.agent.pop('heartbeat_timestamp', None)
+        self.agent.pop('configurations', None)
         for agent in agents:
             agent.pop('heartbeat_timestamp', None)
+            agent.pop('configurations', None)
         self.assertIn(self.agent, agents)
 
     @test.attr(type=['smoke'])
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index 13ae1c0..25e1cc0 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -20,6 +20,7 @@
     _interface = 'json'
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(DHCPAgentSchedulersTestJSON, cls).setUpClass()
         if not test.is_extension_enabled('dhcp_agent_scheduler', 'network'):
diff --git a/tempest/api/network/admin/test_lbaas_agent_scheduler.py b/tempest/api/network/admin/test_lbaas_agent_scheduler.py
index a5ba90f..675c62d 100644
--- a/tempest/api/network/admin/test_lbaas_agent_scheduler.py
+++ b/tempest/api/network/admin/test_lbaas_agent_scheduler.py
@@ -35,6 +35,7 @@
     """
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(LBaaSAgentSchedulerTestJSON, cls).setUpClass()
         if not test.is_extension_enabled('lbaas_agent_scheduler', 'network'):
diff --git a/tempest/api/network/admin/test_load_balancer_admin_actions.py b/tempest/api/network/admin/test_load_balancer_admin_actions.py
index 34a8e32..6bcc118 100644
--- a/tempest/api/network/admin/test_load_balancer_admin_actions.py
+++ b/tempest/api/network/admin/test_load_balancer_admin_actions.py
@@ -29,6 +29,7 @@
     """
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(LoadBalancerAdminTestJSON, cls).setUpClass()
         if not test.is_extension_enabled('lbaas', 'network'):
@@ -89,6 +90,18 @@
         show_health_monitor = body['health_monitor']
         self.assertEqual(health_monitor['id'], show_health_monitor['id'])
 
+    @test.attr(type='smoke')
+    def test_create_pool_from_admin_user_other_tenant(self):
+        resp, body = self.admin_client.create_pool(
+            name=data_utils.rand_name('pool-'), lb_method="ROUND_ROBIN",
+            protocol="HTTP", subnet_id=self.subnet['id'],
+            tenant_id=self.tenant_id)
+        self.assertEqual('201', resp['status'])
+        pool = body['pool']
+        self.addCleanup(self.admin_client.delete_pool, pool['id'])
+        self.assertIsNotNone(pool['id'])
+        self.assertEqual(self.tenant_id, pool['tenant_id'])
+
 
 class LoadBalancerAdminTestXML(LoadBalancerAdminTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/network/test_extra_dhcp_options.py b/tempest/api/network/test_extra_dhcp_options.py
index ed86d75..371c651 100644
--- a/tempest/api/network/test_extra_dhcp_options.py
+++ b/tempest/api/network/test_extra_dhcp_options.py
@@ -36,6 +36,7 @@
     """
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(ExtraDHCPOptionsTestJSON, cls).setUpClass()
         if not test.is_extension_enabled('extra_dhcp_opt', 'network'):
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index 06871ad..7191940 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -44,6 +44,7 @@
     """
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(FloatingIPTestJSON, cls).setUpClass()
         if not test.is_extension_enabled('router', 'network'):
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index 792d61d..3ab015e 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -38,6 +38,7 @@
     """
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(LoadBalancerTestJSON, cls).setUpClass()
         if not test.is_extension_enabled('lbaas', 'network'):
@@ -156,10 +157,14 @@
         # Verification of pool update
         new_name = "New_pool"
         resp, body = self.client.update_pool(pool['id'],
-                                             name=new_name)
+                                             name=new_name,
+                                             description="new_description",
+                                             lb_method='LEAST_CONNECTIONS')
         self.assertEqual('200', resp['status'])
         updated_pool = body['pool']
-        self.assertEqual(updated_pool['name'], new_name)
+        self.assertEqual(new_name, updated_pool['name'])
+        self.assertEqual('new_description', updated_pool['description'])
+        self.assertEqual('LEAST_CONNECTIONS', updated_pool['lb_method'])
         # Verification of pool delete
         resp, body = self.client.delete_pool(pool['id'])
         self.assertEqual('204', resp['status'])
@@ -377,6 +382,58 @@
         self.assertIn("active_connections", stats)
         self.assertIn("bytes_out", stats)
 
+    @test.attr(type='smoke')
+    def test_update_list_of_health_monitors_associated_with_pool(self):
+        resp, _ = (self.client.associate_health_monitor_with_pool
+                   (self.health_monitor['id'], self.pool['id']))
+        self.assertEqual('201', resp['status'])
+        resp, _ = self.client.update_health_monitor(
+            self.health_monitor['id'], admin_state_up=False)
+        self.assertEqual('200', resp['status'])
+        resp, body = self.client.show_pool(self.pool['id'])
+        self.assertEqual('200', resp['status'])
+        health_monitors = body['pool']['health_monitors']
+        for health_monitor_id in health_monitors:
+            resp, body = self.client.show_health_monitor(health_monitor_id)
+            self.assertEqual('200', resp['status'])
+            self.assertFalse(body['health_monitor']['admin_state_up'])
+        resp, _ = (self.client.disassociate_health_monitor_with_pool
+                   (self.health_monitor['id'], self.pool['id']))
+        self.assertEqual('204', resp['status'])
+
+    @test.attr(type='smoke')
+    def test_update_admin_state_up_of_pool(self):
+        resp, _ = self.client.update_pool(self.pool['id'],
+                                          admin_state_up=False)
+        self.assertEqual('200', resp['status'])
+        resp, body = self.client.show_pool(self.pool['id'])
+        self.assertEqual('200', resp['status'])
+        pool = body['pool']
+        self.assertFalse(pool['admin_state_up'])
+
+    @test.attr(type='smoke')
+    def test_show_vip_associated_with_pool(self):
+        resp, body = self.client.show_pool(self.pool['id'])
+        self.assertEqual('200', resp['status'])
+        pool = body['pool']
+        resp, body = self.client.show_vip(pool['vip_id'])
+        self.assertEqual('200', resp['status'])
+        vip = body['vip']
+        self.assertEqual(self.vip['name'], vip['name'])
+        self.assertEqual(self.vip['id'], vip['id'])
+
+    @test.attr(type='smoke')
+    def test_show_members_associated_with_pool(self):
+        resp, body = self.client.show_pool(self.pool['id'])
+        self.assertEqual('200', resp['status'])
+        members = body['pool']['members']
+        for member_id in members:
+            resp, body = self.client.show_member(member_id)
+            self.assertEqual('200', resp['status'])
+            self.assertIsNotNone(body['member']['status'])
+            self.assertEqual(member_id, body['member']['id'])
+            self.assertIsNotNone(body['member']['admin_state_up'])
+
 
 class LoadBalancerTestXML(LoadBalancerTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index b9041ee..0175de7 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -19,7 +19,7 @@
 from tempest.common.utils import data_utils
 from tempest import config
 from tempest import exceptions
-from tempest.test import attr
+from tempest import test
 
 CONF = config.CONF
 
@@ -64,6 +64,7 @@
     """
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(NetworksTestJSON, cls).setUpClass()
         cls.network = cls.create_network()
@@ -71,7 +72,7 @@
         cls.subnet = cls.create_subnet(cls.network)
         cls.cidr = cls.subnet['cidr']
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_create_update_delete_network_subnet(self):
         # Create a network
         name = data_utils.rand_name('network-')
@@ -102,7 +103,7 @@
         resp, body = self.client.delete_network(net_id)
         self.assertEqual('204', resp['status'])
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_show_network(self):
         # Verify the details of a network
         resp, body = self.client.show_network(self.network['id'])
@@ -111,7 +112,7 @@
         for key in ['id', 'name']:
             self.assertEqual(network[key], self.network[key])
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_show_network_fields(self):
         # Verify specific fields of a network
         field_list = [('fields', 'id'), ('fields', 'name'), ]
@@ -123,7 +124,7 @@
         for label, field_name in field_list:
             self.assertEqual(network[field_name], self.network[field_name])
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_list_networks(self):
         # Verify the network exists in the list of all networks
         resp, body = self.client.list_networks()
@@ -132,7 +133,7 @@
                     if network['id'] == self.network['id']]
         self.assertNotEmpty(networks, "Created network not found in the list")
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_list_networks_fields(self):
         # Verify specific fields of the networks
         resp, body = self.client.list_networks(fields='id')
@@ -143,7 +144,7 @@
             self.assertEqual(len(network), 1)
             self.assertIn('id', network)
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_show_subnet(self):
         # Verify the details of a subnet
         resp, body = self.client.show_subnet(self.subnet['id'])
@@ -154,7 +155,7 @@
             self.assertIn(key, subnet)
             self.assertEqual(subnet[key], self.subnet[key])
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_show_subnet_fields(self):
         # Verify specific fields of a subnet
         field_list = [('fields', 'id'), ('fields', 'cidr'), ]
@@ -166,7 +167,7 @@
         for label, field_name in field_list:
             self.assertEqual(subnet[field_name], self.subnet[field_name])
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_list_subnets(self):
         # Verify the subnet exists in the list of all subnets
         resp, body = self.client.list_subnets()
@@ -175,7 +176,7 @@
                    if subnet['id'] == self.subnet['id']]
         self.assertNotEmpty(subnets, "Created subnet not found in the list")
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_list_subnets_fields(self):
         # Verify specific fields of subnets
         resp, body = self.client.list_subnets(fields='id')
@@ -194,7 +195,7 @@
         except exceptions.NotFound:
             pass
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_delete_network_with_subnet(self):
         # Creates a network
         name = data_utils.rand_name('network-')
@@ -221,7 +222,7 @@
         # it from the list.
         self.subnets.pop()
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_create_port_with_no_ip(self):
         # For this test create a new network - do not use any previously
         # created networks.
@@ -275,6 +276,7 @@
     """
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(BulkNetworkOpsTestJSON, cls).setUpClass()
         cls.network1 = cls.create_network()
@@ -310,7 +312,7 @@
         for n in created_ports:
             self.assertNotIn(n['id'], ports_list)
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_bulk_create_delete_network(self):
         # Creates 2 networks in one request
         network_names = [data_utils.rand_name('network-'),
@@ -326,7 +328,7 @@
             self.assertIsNotNone(n['id'])
             self.assertIn(n['id'], networks_list)
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_bulk_create_delete_subnet(self):
         # Creates 2 subnets in one request
         cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
@@ -358,7 +360,7 @@
             self.assertIsNotNone(n['id'])
             self.assertIn(n['id'], subnets_list)
 
-    @attr(type='smoke')
+    @test.attr(type='smoke')
     def test_bulk_create_delete_port(self):
         # Creates 2 ports in one request
         networks = [self.network1['id'], self.network2['id']]
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index fbb25a8..66dcaa5 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -27,6 +27,7 @@
     _interface = 'json'
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(PortsTestJSON, cls).setUpClass()
         cls.network = cls.create_network()
@@ -143,6 +144,7 @@
     _interface = 'json'
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(PortsAdminExtendedAttrsTestJSON, cls).setUpClass()
         cls.identity_client = cls._get_identity_admin_client()
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index e6ad4de..91ab9d6 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -23,6 +23,7 @@
     _interface = 'json'
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(RoutersNegativeTest, cls).setUpClass()
         if not test.is_extension_enabled('router', 'network'):
diff --git a/tempest/api/network/test_vpnaas_extensions.py b/tempest/api/network/test_vpnaas_extensions.py
index 78bc80a..f64bd33 100644
--- a/tempest/api/network/test_vpnaas_extensions.py
+++ b/tempest/api/network/test_vpnaas_extensions.py
@@ -37,6 +37,7 @@
     """
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         if not test.is_extension_enabled('vpnaas', 'network'):
             msg = "vpnaas extension not enabled."
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index a3098a5..d919245 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -82,7 +82,8 @@
         # Set a quota of 20 bytes on the user's account before each test
         headers = {"X-Account-Meta-Quota-Bytes": "20"}
 
-        self.os.custom_account_client.request("POST", "", headers, "")
+        self.os.custom_account_client.request("POST", url="", headers=headers,
+                                              body="")
 
     def tearDown(self):
         # Set the reselleradmin auth in headers for next custom_account_client
@@ -94,7 +95,8 @@
         # remove the quota from the container
         headers = {"X-Remove-Account-Meta-Quota-Bytes": "x"}
 
-        self.os.custom_account_client.request("POST", "", headers, "")
+        self.os.custom_account_client.request("POST", url="", headers=headers,
+                                              body="")
         super(AccountQuotasTest, self).tearDown()
 
     @classmethod
@@ -135,8 +137,9 @@
             )
             headers = {"X-Account-Meta-Quota-Bytes": quota}
 
-            resp, _ = self.os.custom_account_client.request("POST", "",
-                                                            headers, "")
+            resp, _ = self.os.custom_account_client.request("POST", url="",
+                                                            headers=headers,
+                                                            body="")
 
             self.assertEqual(resp["status"], "204")
             self.assertHeaders(resp, 'Account', 'POST')
diff --git a/tempest/api/object_storage/test_account_quotas_negative.py b/tempest/api/object_storage/test_account_quotas_negative.py
index 7648ea1..29afebc 100644
--- a/tempest/api/object_storage/test_account_quotas_negative.py
+++ b/tempest/api/object_storage/test_account_quotas_negative.py
@@ -81,7 +81,8 @@
         # Set a quota of 20 bytes on the user's account before each test
         headers = {"X-Account-Meta-Quota-Bytes": "20"}
 
-        self.os.custom_account_client.request("POST", "", headers, "")
+        self.os.custom_account_client.request("POST", url="", headers=headers,
+                                              body="")
 
     def tearDown(self):
         # Set the reselleradmin auth in headers for next custom_account_client
@@ -93,7 +94,8 @@
         # remove the quota from the container
         headers = {"X-Remove-Account-Meta-Quota-Bytes": "x"}
 
-        self.os.custom_account_client.request("POST", "", headers, "")
+        self.os.custom_account_client.request("POST", url="", headers=headers,
+                                              body="")
         super(AccountQuotasNegativeTest, self).tearDown()
 
     @classmethod
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 91df292..b057698 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 import hashlib
+import re
 from six import moves
 
 from tempest.api.object_storage import base
@@ -35,6 +36,29 @@
         cls.delete_containers(cls.containers)
         super(ObjectTest, cls).tearDownClass()
 
+    def _create_object(self, metadata=None):
+        # setup object
+        object_name = data_utils.rand_name(name='TestObject')
+        data = data_utils.arbitrary_string()
+        self.object_client.create_object(self.container_name,
+                                         object_name, data, metadata=metadata)
+
+        return object_name, data
+
+    def _upload_segments(self):
+        # create object
+        object_name = data_utils.rand_name(name='LObject')
+        data = data_utils.arbitrary_string()
+        segments = 10
+        data_segments = [data + str(i) for i in moves.xrange(segments)]
+        # uploading segments
+        for i in moves.xrange(segments):
+            resp, _ = self.object_client.create_object_segments(
+                self.container_name, object_name, i, data_segments[i])
+            self.assertEqual(resp['status'], '201')
+
+        return object_name, data_segments
+
     @test.attr(type='smoke')
     def test_create_object(self):
         # create object
@@ -64,32 +88,220 @@
         self.assertHeaders(resp, 'Object', 'DELETE')
 
     @test.attr(type='smoke')
-    def test_object_metadata(self):
-        # add metadata to storage object, test if metadata is retrievable
+    def test_update_object_metadata(self):
+        # update object metadata
+        object_name, data = self._create_object()
 
-        # create Object
-        object_name = data_utils.rand_name(name='TestObject')
-        data = data_utils.arbitrary_string()
-        resp, _ = self.object_client.create_object(self.container_name,
-                                                   object_name, data)
-        # set object metadata
-        meta_key = data_utils.rand_name(name='test-')
-        meta_value = data_utils.rand_name(name='MetaValue-')
-        orig_metadata = {meta_key: meta_value}
+        metadata = {'X-Object-Meta-test-meta': 'Meta'}
         resp, _ = self.object_client.update_object_metadata(
-            self.container_name, object_name, orig_metadata)
+            self.container_name,
+            object_name,
+            metadata,
+            metadata_prefix='')
         self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
         self.assertHeaders(resp, 'Object', 'POST')
 
+        resp, _ = self.object_client.list_object_metadata(
+            self.container_name,
+            object_name)
+        self.assertIn('x-object-meta-test-meta', resp)
+        self.assertEqual(resp['x-object-meta-test-meta'], 'Meta')
+
+    def test_update_object_metadata_with_remove_metadata(self):
+        # update object metadata with remove metadata
+        object_name = data_utils.rand_name(name='TestObject')
+        data = data_utils.arbitrary_string()
+        create_metadata = {'X-Object-Meta-test-meta1': 'Meta1'}
+        self.object_client.create_object(self.container_name,
+                                         object_name,
+                                         data,
+                                         metadata=create_metadata)
+
+        update_metadata = {'X-Remove-Object-Meta-test-meta1': 'Meta1'}
+        resp, _ = self.object_client.update_object_metadata(
+            self.container_name,
+            object_name,
+            update_metadata,
+            metadata_prefix='')
+        self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'POST')
+
+        resp, _ = self.object_client.list_object_metadata(
+            self.container_name,
+            object_name)
+        self.assertNotIn('x-object-meta-test-meta1', resp)
+
+    @test.attr(type='smoke')
+    def test_update_object_metadata_with_create_and_remove_metadata(self):
+        # creation and deletion of metadata with one request
+        object_name = data_utils.rand_name(name='TestObject')
+        data = data_utils.arbitrary_string()
+        create_metadata = {'X-Object-Meta-test-meta1': 'Meta1'}
+        self.object_client.create_object(self.container_name,
+                                         object_name,
+                                         data,
+                                         metadata=create_metadata)
+
+        update_metadata = {'X-Object-Meta-test-meta2': 'Meta2',
+                           'X-Remove-Object-Meta-test-meta1': 'Meta1'}
+        resp, _ = self.object_client.update_object_metadata(
+            self.container_name,
+            object_name,
+            update_metadata,
+            metadata_prefix='')
+        self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'POST')
+
+        resp, _ = self.object_client.list_object_metadata(
+            self.container_name,
+            object_name)
+        self.assertNotIn('x-object-meta-test-meta1', resp)
+        self.assertIn('x-object-meta-test-meta2', resp)
+        self.assertEqual(resp['x-object-meta-test-meta2'], 'Meta2')
+
+    @test.attr(type='smoke')
+    def test_update_object_metadata_with_x_object_manifest(self):
+        # update object metadata with x_object_manifest
+
+        # uploading segments
+        object_name, data_segments = self._upload_segments()
+        # creating a manifest file
+        data_empty = ''
+        self.object_client.create_object(self.container_name,
+                                         object_name,
+                                         data_empty,
+                                         metadata=None)
+        object_prefix = '%s/%s' % (self.container_name, object_name)
+        update_metadata = {'X-Object-Manifest': object_prefix}
+        resp, _ = self.object_client.update_object_metadata(
+            self.container_name,
+            object_name,
+            update_metadata,
+            metadata_prefix='')
+        self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'POST')
+
+        resp, _ = self.object_client.list_object_metadata(
+            self.container_name,
+            object_name)
+        self.assertIn('x-object-manifest', resp)
+        self.assertNotEqual(len(resp['x-object-manifest']), 0)
+
+    def test_update_object_metadata_with_x_object_metakey(self):
+        # update object metadata with a blenk value of metadata
+        object_name, data = self._create_object()
+
+        update_metadata = {'X-Object-Meta-test-meta': ''}
+        resp, _ = self.object_client.update_object_metadata(
+            self.container_name,
+            object_name,
+            update_metadata,
+            metadata_prefix='')
+        self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'POST')
+
+        resp, _ = self.object_client.list_object_metadata(
+            self.container_name,
+            object_name)
+        self.assertIn('x-object-meta-test-meta', resp)
+        self.assertEqual(resp['x-object-meta-test-meta'], '')
+
+    @test.attr(type='smoke')
+    def test_update_object_metadata_with_x_remove_object_metakey(self):
+        # update object metadata with a blank value of remove metadata
+        object_name = data_utils.rand_name(name='TestObject')
+        data = data_utils.arbitrary_string()
+        create_metadata = {'X-Object-Meta-test-meta': 'Meta'}
+        self.object_client.create_object(self.container_name,
+                                         object_name,
+                                         data,
+                                         metadata=create_metadata)
+
+        update_metadata = {'X-Remove-Object-Meta-test-meta': ''}
+        resp, _ = self.object_client.update_object_metadata(
+            self.container_name,
+            object_name,
+            update_metadata,
+            metadata_prefix='')
+        self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'POST')
+
+        resp, _ = self.object_client.list_object_metadata(
+            self.container_name,
+            object_name)
+        self.assertNotIn('x-object-meta-test-meta', resp)
+
+    @test.attr(type='smoke')
+    def test_list_object_metadata(self):
         # get object metadata
-        resp, resp_metadata = self.object_client.list_object_metadata(
-            self.container_name, object_name)
+        object_name = data_utils.rand_name(name='TestObject')
+        data = data_utils.arbitrary_string()
+        metadata = {'X-Object-Meta-test-meta': 'Meta'}
+        self.object_client.create_object(self.container_name,
+                                         object_name,
+                                         data,
+                                         metadata=metadata)
+
+        resp, _ = self.object_client.list_object_metadata(
+            self.container_name,
+            object_name)
         self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
         self.assertHeaders(resp, 'Object', 'HEAD')
+        self.assertIn('x-object-meta-test-meta', resp)
+        self.assertEqual(resp['x-object-meta-test-meta'], 'Meta')
 
-        actual_meta_key = 'x-object-meta-' + meta_key
-        self.assertIn(actual_meta_key, resp)
-        self.assertEqual(resp[actual_meta_key], meta_value)
+    @test.attr(type='smoke')
+    def test_list_no_object_metadata(self):
+        # get empty list of object metadata
+        object_name, data = self._create_object()
+
+        resp, _ = self.object_client.list_object_metadata(
+            self.container_name,
+            object_name)
+        self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+        self.assertHeaders(resp, 'Object', 'HEAD')
+        self.assertNotIn('x-object-meta-', str(resp))
+
+    @test.attr(type='smoke')
+    def test_list_object_metadata_with_x_object_manifest(self):
+        # get object metadata with x_object_manifest
+
+        # uploading segments
+        object_name, data_segments = self._upload_segments()
+        # creating a manifest file
+        object_prefix = '%s/%s' % (self.container_name, object_name)
+        metadata = {'X-Object-Manifest': object_prefix}
+        data_empty = ''
+        resp, _ = self.object_client.create_object(
+            self.container_name,
+            object_name,
+            data_empty,
+            metadata=metadata)
+
+        resp, _ = self.object_client.list_object_metadata(
+            self.container_name,
+            object_name)
+        self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+
+        # Check only the existence of common headers with custom matcher
+        self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders(
+                        'Object', 'HEAD'))
+        self.assertIn('x-object-manifest', resp)
+
+        # Etag value of a large object is enclosed in double-quotations.
+        # This is a special case, therefore the formats of response headers
+        # are checked without a custom matcher.
+        self.assertTrue(resp['etag'].startswith('\"'))
+        self.assertTrue(resp['etag'].endswith('\"'))
+        self.assertTrue(resp['etag'].strip('\"').isalnum())
+        self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
+        self.assertNotEqual(len(resp['content-type']), 0)
+        self.assertTrue(re.match("^tx[0-9a-f]*-[0-9a-f]*$",
+                                 resp['x-trans-id']))
+        self.assertNotEqual(len(resp['date']), 0)
+        self.assertEqual(resp['accept-ranges'], 'bytes')
+        self.assertEqual(resp['x-object-manifest'],
+                         '%s/%s' % (self.container_name, object_name))
 
     @test.attr(type='smoke')
     def test_get_object(self):
diff --git a/tempest/api/orchestration/stacks/test_server_cfn_init.py b/tempest/api/orchestration/stacks/test_server_cfn_init.py
index 5f65193..3f29269 100644
--- a/tempest/api/orchestration/stacks/test_server_cfn_init.py
+++ b/tempest/api/orchestration/stacks/test_server_cfn_init.py
@@ -93,7 +93,8 @@
         try:
             self.client.wait_for_resource_status(
                 sid, 'WaitCondition', 'CREATE_COMPLETE')
-        except exceptions.TimeoutException as e:
+        except (exceptions.StackResourceBuildErrorException,
+                exceptions.TimeoutException) as e:
             # attempt to log the server console to help with debugging
             # the cause of the server not signalling the waitcondition
             # to heat.
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index be5d76b..58da440 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -51,8 +51,7 @@
         v_name = data_utils.rand_name('Volume')
         metadata = {'Type': 'Test'}
         # Create a volume
-        resp, volume = self.client.create_volume(size=1,
-                                                 display_name=v_name,
+        resp, volume = self.client.create_volume(display_name=v_name,
                                                  metadata=metadata,
                                                  **kwargs)
         self.assertEqual(200, resp.status)
diff --git a/tempest/api_schema/compute/aggregates.py b/tempest/api_schema/compute/aggregates.py
index 12da618..a70b356 100644
--- a/tempest/api_schema/compute/aggregates.py
+++ b/tempest/api_schema/compute/aggregates.py
@@ -54,3 +54,5 @@
         'required': ['aggregate']
     }
 }
+
+aggregate_set_metadata = get_aggregate
diff --git a/tempest/api_schema/compute/flavors.py b/tempest/api_schema/compute/flavors.py
index a6367d4..fd02780 100644
--- a/tempest/api_schema/compute/flavors.py
+++ b/tempest/api_schema/compute/flavors.py
@@ -35,3 +35,30 @@
         'required': ['flavors']
     }
 }
+
+common_flavor_list_details = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'flavors': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'name': {'type': 'string'},
+                        'links': parameter_types.links,
+                        'ram': {'type': 'integer'},
+                        'vcpus': {'type': 'integer'},
+                        'swap': {'type': 'integer'},
+                        'disk': {'type': 'integer'},
+                        'id': {'type': 'string'}
+                    },
+                    'required': ['name', 'links', 'ram', 'vcpus',
+                                 'swap', 'disk', 'id']
+                }
+            }
+        },
+        'required': ['flavors']
+    }
+}
diff --git a/tempest/api_schema/compute/flavors_access.py b/tempest/api_schema/compute/flavors_access.py
index 152e24c..cd31b0a 100644
--- a/tempest/api_schema/compute/flavors_access.py
+++ b/tempest/api_schema/compute/flavors_access.py
@@ -12,7 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-list_flavor_access = {
+add_remove_list_flavor_access = {
     'status_code': [200],
     'response_body': {
         'type': 'object',
diff --git a/tempest/api_schema/compute/v2/flavors.py b/tempest/api_schema/compute/v2/flavors.py
new file mode 100644
index 0000000..999ca19
--- /dev/null
+++ b/tempest/api_schema/compute/v2/flavors.py
@@ -0,0 +1,33 @@
+# Copyright 2014 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 copy
+
+from tempest.api_schema.compute import flavors
+
+list_flavors_details = copy.deepcopy(flavors.common_flavor_list_details)
+
+# 'swap' attributes comes as integre value but if it is empty it comes as "".
+# So defining type of as string and integer.
+list_flavors_details['response_body']['properties']['flavors']['items'][
+    'properties']['swap'] = {'type': ['string', 'integer']}
+
+# Defining extra attributes for V2 flavor schema
+list_flavors_details['response_body']['properties']['flavors']['items'][
+    'properties'].update({'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
+                          'os-flavor-access:is_public': {'type': 'boolean'},
+                          'rxtx_factor': {'type': 'number'},
+                          'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'}})
+# 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and 'OS-FLV-EXT-DATA'
+# are API extensions. So they are not 'required'.
diff --git a/tempest/api_schema/compute/v3/flavors.py b/tempest/api_schema/compute/v3/flavors.py
new file mode 100644
index 0000000..542d2b1
--- /dev/null
+++ b/tempest/api_schema/compute/v3/flavors.py
@@ -0,0 +1,33 @@
+# Copyright 2014 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 copy
+
+from tempest.api_schema.compute import flavors
+
+list_flavors_details = copy.deepcopy(flavors.common_flavor_list_details)
+
+# NOTE- In v3 API, 'swap' comes as '0' not empty string '""'
+# (In V2 API, it comes as empty string) So leaving 'swap'as integer type only.
+
+# Defining extra attributes for V3 flavor schema
+list_flavors_details['response_body']['properties']['flavors']['items'][
+    'properties'].update({'disabled': {'type': 'boolean'},
+                          'ephemeral': {'type': 'integer'},
+                          'flavor-access:is_public': {'type': 'boolean'},
+                          'os-flavor-rxtx:rxtx_factor': {'type': 'number'}})
+# 'flavor-access' and 'os-flavor-rxtx' are API extensions.
+# So they are not 'required'.
+list_flavors_details['response_body']['properties']['flavors']['items'][
+    'required'].extend(['disabled', 'ephemeral'])
diff --git a/tempest/cli/simple_read_only/test_sahara.py b/tempest/cli/simple_read_only/test_sahara.py
index 5af8dae..36cc324 100644
--- a/tempest/cli/simple_read_only/test_sahara.py
+++ b/tempest/cli/simple_read_only/test_sahara.py
@@ -93,3 +93,48 @@
             'status',
             'node_count'
         ])
+
+    def test_sahara_data_source_list(self):
+        result = self.sahara('data-source-list')
+        data_sources = self.parser.listing(result)
+        self.assertTableStruct(data_sources, [
+            'name',
+            'id',
+            'type',
+            'description'
+        ])
+
+    def test_sahara_job_binary_data_list(self):
+        result = self.sahara('job-binary-data-list')
+        job_binary_data_list = self.parser.listing(result)
+        self.assertTableStruct(job_binary_data_list, [
+            'id',
+            'name'
+        ])
+
+    def test_sahara_job_binary_list(self):
+        result = self.sahara('job-binary-list')
+        job_binaries = self.parser.listing(result)
+        self.assertTableStruct(job_binaries, [
+            'id',
+            'name',
+            'description'
+        ])
+
+    def test_sahara_job_template_list(self):
+        result = self.sahara('job-template-list')
+        job_templates = self.parser.listing(result)
+        self.assertTableStruct(job_templates, [
+            'id',
+            'name',
+            'description'
+        ])
+
+    def test_sahara_job_list(self):
+        result = self.sahara('job-list')
+        jobs = self.parser.listing(result)
+        self.assertTableStruct(jobs, [
+            'id',
+            'cluster_id',
+            'status'
+        ])
diff --git a/tempest/clients.py b/tempest/clients.py
index 693ca41..10c0014 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -13,15 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-# Default client libs
-import cinderclient.client
-import glanceclient
-import heatclient.client
 import keystoneclient.exceptions
 import keystoneclient.v2_0.client
-import neutronclient.v2_0.client
-import novaclient.client
-import swiftclient
 
 from tempest.common.rest_client import NegativeRestClient
 from tempest import config
@@ -463,6 +456,8 @@
     NOVACLIENT_VERSION = '2'
     CINDERCLIENT_VERSION = '1'
     HEATCLIENT_VERSION = '1'
+    IRONICCLIENT_VERSION = '1'
+    SAHARACLIENT_VERSION = '1.1'
 
     def __init__(self, username, password, tenant_name):
         # FIXME(andreaf) Auth provider for client_type 'official' is
@@ -472,6 +467,7 @@
         # super cares for credentials validation
         super(OfficialClientManager, self).__init__(
             username=username, password=password, tenant_name=tenant_name)
+        self.baremetal_client = self._get_baremetal_client()
         self.compute_client = self._get_compute_client(username,
                                                        password,
                                                        tenant_name)
@@ -491,11 +487,34 @@
             username,
             password,
             tenant_name)
+        self.data_processing_client = self._get_data_processing_client(
+            username,
+            password,
+            tenant_name)
+
+    def _get_roles(self):
+        keystone_admin = self._get_identity_client(
+            CONF.identity.admin_username,
+            CONF.identity.admin_password,
+            CONF.identity.admin_tenant_name)
+
+        username = self.credentials['username']
+        tenant_name = self.credentials['tenant_name']
+        user_id = keystone_admin.users.find(name=username).id
+        tenant_id = keystone_admin.tenants.find(name=tenant_name).id
+
+        roles = keystone_admin.roles.roles_for_user(
+            user=user_id, tenant=tenant_id)
+
+        return [r.name for r in roles]
 
     def _get_compute_client(self, username, password, tenant_name):
         # Novaclient will not execute operations for anyone but the
         # identified user, so a new client needs to be created for
         # each user that operations need to be performed for.
+        if not CONF.service_available.nova:
+            return None
+        import novaclient.client
         self._validate_credentials(username, password, tenant_name)
 
         auth_url = CONF.identity.uri
@@ -517,6 +536,9 @@
                                         http_log_debug=True)
 
     def _get_image_client(self):
+        if not CONF.service_available.glance:
+            return None
+        import glanceclient
         token = self.identity_client.auth_token
         region = CONF.identity.region
         endpoint_type = CONF.image.endpoint_type
@@ -528,9 +550,13 @@
                                    insecure=dscv)
 
     def _get_volume_client(self, username, password, tenant_name):
+        if not CONF.service_available.cinder:
+            return None
+        import cinderclient.client
         auth_url = CONF.identity.uri
         region = CONF.identity.region
         endpoint_type = CONF.volume.endpoint_type
+        dscv = CONF.identity.disable_ssl_certificate_validation
         return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
                                           username,
                                           password,
@@ -538,9 +564,13 @@
                                           auth_url,
                                           region_name=region,
                                           endpoint_type=endpoint_type,
+                                          insecure=dscv,
                                           http_log_debug=True)
 
     def _get_object_storage_client(self, username, password, tenant_name):
+        if not CONF.service_available.swift:
+            return None
+        import swiftclient
         auth_url = CONF.identity.uri
         # add current tenant to swift operator role group.
         keystone_admin = self._get_identity_client(
@@ -570,6 +600,9 @@
 
     def _get_orchestration_client(self, username=None, password=None,
                                   tenant_name=None):
+        if not CONF.service_available.heat:
+            return None
+        import heatclient.client
         if not username:
             username = CONF.identity.admin_username
         if not password:
@@ -613,6 +646,37 @@
                                                  auth_url=auth_url,
                                                  insecure=dscv)
 
+    def _get_baremetal_client(self):
+        # ironic client is currently intended to by used by admin users
+        if not CONF.service_available.ironic:
+            return None
+        import ironicclient.client
+        roles = self._get_roles()
+        if CONF.identity.admin_role not in roles:
+            return None
+
+        auth_url = CONF.identity.uri
+        api_version = self.IRONICCLIENT_VERSION
+        insecure = CONF.identity.disable_ssl_certificate_validation
+        service_type = CONF.baremetal.catalog_type
+        endpoint_type = CONF.baremetal.endpoint_type
+        creds = {
+            'os_username': self.credentials['username'],
+            'os_password': self.credentials['password'],
+            'os_tenant_name': self.credentials['tenant_name']
+        }
+
+        try:
+            return ironicclient.client.get_client(
+                api_version=api_version,
+                os_auth_url=auth_url,
+                insecure=insecure,
+                os_service_type=service_type,
+                os_endpoint_type=endpoint_type,
+                **creds)
+        except keystoneclient.exceptions.EndpointNotFound:
+            return None
+
     def _get_network_client(self):
         # The intended configuration is for the network client to have
         # admin privileges and indicate for whom resources are being
@@ -620,6 +684,9 @@
         # preferable to authenticating as a specific user because
         # working with certain resources (public routers and networks)
         # often requires admin privileges anyway.
+        if not CONF.service_available.neutron:
+            return None
+        import neutronclient.v2_0.client
         username = CONF.identity.admin_username
         password = CONF.identity.admin_password
         tenant_name = CONF.identity.admin_tenant_name
@@ -636,3 +703,25 @@
                                                 endpoint_type=endpoint_type,
                                                 auth_url=auth_url,
                                                 insecure=dscv)
+
+    def _get_data_processing_client(self, username, password, tenant_name):
+        if not CONF.service_available.sahara:
+            # Sahara isn't available
+            return None
+
+        import saharaclient.client
+
+        self._validate_credentials(username, password, tenant_name)
+
+        endpoint_type = CONF.data_processing.endpoint_type
+        catalog_type = CONF.data_processing.catalog_type
+        auth_url = CONF.identity.uri
+
+        client = saharaclient.client.Client(self.SAHARACLIENT_VERSION,
+                                            username, password,
+                                            project_name=tenant_name,
+                                            endpoint_type=endpoint_type,
+                                            service_type=catalog_type,
+                                            auth_url=auth_url)
+
+        return client
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 830968e..0d32f41 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -197,26 +197,26 @@
                 details = pattern.format(read_code, expected_code)
                 raise exceptions.InvalidHttpSuccessCode(details)
 
-    def post(self, url, body, headers=None):
-        return self.request('POST', url, headers, body)
+    def post(self, url, body, headers=None, extra_headers=False):
+        return self.request('POST', url, extra_headers, headers, body)
 
-    def get(self, url, headers=None):
-        return self.request('GET', url, headers)
+    def get(self, url, headers=None, extra_headers=False):
+        return self.request('GET', url, extra_headers, headers)
 
-    def delete(self, url, headers=None, body=None):
-        return self.request('DELETE', url, headers, body)
+    def delete(self, url, headers=None, body=None, extra_headers=False):
+        return self.request('DELETE', url, extra_headers, headers, body)
 
-    def patch(self, url, body, headers=None):
-        return self.request('PATCH', url, headers, body)
+    def patch(self, url, body, headers=None, extra_headers=False):
+        return self.request('PATCH', url, extra_headers, headers, body)
 
-    def put(self, url, body, headers=None):
-        return self.request('PUT', url, headers, body)
+    def put(self, url, body, headers=None, extra_headers=False):
+        return self.request('PUT', url, extra_headers, headers, body)
 
-    def head(self, url, headers=None):
-        return self.request('HEAD', url, headers)
+    def head(self, url, headers=None, extra_headers=False):
+        return self.request('HEAD', url, extra_headers, headers)
 
-    def copy(self, url, headers=None):
-        return self.request('COPY', url, headers)
+    def copy(self, url, headers=None, extra_headers=False):
+        return self.request('COPY', url, extra_headers, headers)
 
     def get_versions(self):
         resp, body = self.get('')
@@ -418,13 +418,22 @@
 
         return resp, resp_body
 
-    def request(self, method, url, headers=None, body=None):
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None):
+        # if extra_headers is True
+        # default headers would be added to headers
         retry = 0
 
         if headers is None:
             # NOTE(vponomaryov): if some client do not need headers,
             # it should explicitly pass empty dict
             headers = self.get_headers()
+        elif extra_headers:
+            try:
+                headers = headers.copy()
+                headers.update(self.get_headers())
+            except (ValueError, TypeError):
+                headers = self.get_headers()
 
         resp, resp_body = self._request(method, url,
                                         headers=headers, body=body)
diff --git a/tempest/config.py b/tempest/config.py
index 0212d8a..c0ab323 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -126,7 +126,7 @@
                      "OpenStack Identity API admin credentials are known."),
     cfg.StrOpt('image_ref',
                default="{$IMAGE_ID}",
-               help="Valid secondary image reference to be used in tests."),
+               help="Valid primary image reference to be used in tests."),
     cfg.StrOpt('image_ref_alt',
                default="{$IMAGE_ID_ALT}",
                help="Valid secondary image reference to be used in tests."),
@@ -441,6 +441,9 @@
     cfg.StrOpt('disk_format',
                default='raw',
                help='Disk format to use when copying a volume to image'),
+    cfg.IntOpt('volume_size',
+               default=1,
+               help='Default size in GB for volumes created by volumes tests'),
 ]
 
 volume_feature_group = cfg.OptGroup(name='volume-feature-enabled',
@@ -844,13 +847,29 @@
 BaremetalGroup = [
     cfg.StrOpt('catalog_type',
                default='baremetal',
-               help="Catalog type of the baremetal provisioning service."),
+               help="Catalog type of the baremetal provisioning service"),
+    cfg.BoolOpt('driver_enabled',
+                default=False,
+                help="Whether the Ironic nova-compute driver is enabled"),
     cfg.StrOpt('endpoint_type',
                default='publicURL',
                choices=['public', 'admin', 'internal',
                         'publicURL', 'adminURL', 'internalURL'],
                help="The endpoint type to use for the baremetal provisioning "
-                    "service."),
+                    "service"),
+    cfg.IntOpt('active_timeout',
+               default=300,
+               help="Timeout for Ironic node to completely provision"),
+    cfg.IntOpt('association_timeout',
+               default=10,
+               help="Timeout for association of Nova instance and Ironic "
+                    "node"),
+    cfg.IntOpt('power_timeout',
+               default=20,
+               help="Timeout for Ironic power transitions."),
+    cfg.IntOpt('unprovision_timeout',
+               default=20,
+               help="Timeout for unprovisioning an Ironic node.")
 ]
 
 cli_group = cfg.OptGroup(name='cli', title="cli Configuration Options")
diff --git a/tempest/exceptions/__init__.py b/tempest/exceptions/__init__.py
index d313def..9eb9c1e 100644
--- a/tempest/exceptions/__init__.py
+++ b/tempest/exceptions/__init__.py
@@ -78,6 +78,12 @@
                "due to '%(stack_status_reason)s'")
 
 
+class StackResourceBuildErrorException(base.TempestException):
+    message = ("Resource %(resource_name) in stack %(stack_identifier)s is "
+               "in %(resource_status)s status due to "
+               "'%(resource_status_reason)s'")
+
+
 class BadRequest(base.RestClientException):
     message = "Bad request"
 
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index f06a850..5895c37 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -19,6 +19,7 @@
 import six
 import subprocess
 
+from ironicclient import exc as ironic_exceptions
 import netaddr
 from neutronclient.common import exceptions as exc
 from novaclient import exceptions as nova_exceptions
@@ -71,11 +72,13 @@
             username, password, tenant_name)
         cls.compute_client = cls.manager.compute_client
         cls.image_client = cls.manager.image_client
+        cls.baremetal_client = cls.manager.baremetal_client
         cls.identity_client = cls.manager.identity_client
         cls.network_client = cls.manager.network_client
         cls.volume_client = cls.manager.volume_client
         cls.object_storage_client = cls.manager.object_storage_client
         cls.orchestration_client = cls.manager.orchestration_client
+        cls.data_processing_client = cls.manager.data_processing_client
         cls.resource_keys = {}
         cls.os_resources = []
 
@@ -283,7 +286,7 @@
         return rules
 
     def create_server(self, client=None, name=None, image=None, flavor=None,
-                      create_kwargs={}):
+                      wait=True, create_kwargs={}):
         if client is None:
             client = self.compute_client
         if name is None:
@@ -318,7 +321,8 @@
         server = client.servers.create(name, image, flavor, **create_kwargs)
         self.assertEqual(server.name, name)
         self.set_resource(name, server)
-        self.status_timeout(client.servers, server.id, 'ACTIVE')
+        if wait:
+            self.status_timeout(client.servers, server.id, 'ACTIVE')
         # The instance retrieved on creation is missing network
         # details, necessitating retrieval after it becomes active to
         # ensure correct details.
@@ -439,6 +443,80 @@
         LOG.debug("image:%s" % self.image)
 
 
+class BaremetalScenarioTest(OfficialClientTest):
+    @classmethod
+    def setUpClass(cls):
+        super(BaremetalScenarioTest, cls).setUpClass()
+
+        if (not CONF.service_available.ironic or
+           not CONF.baremetal.driver_enabled):
+            msg = 'Ironic not available or Ironic compute driver not enabled'
+            raise cls.skipException(msg)
+
+        # use an admin client manager for baremetal client
+        username, password, tenant = cls.admin_credentials()
+        manager = clients.OfficialClientManager(username, password, tenant)
+        cls.baremetal_client = manager.baremetal_client
+
+        # allow any issues obtaining the node list to raise early
+        cls.baremetal_client.node.list()
+
+    def _node_state_timeout(self, node_id, state_attr,
+                            target_states, timeout=10, interval=1):
+        if not isinstance(target_states, list):
+            target_states = [target_states]
+
+        def check_state():
+            node = self.get_node(node_id=node_id)
+            if getattr(node, state_attr) in target_states:
+                return True
+            return False
+
+        if not tempest.test.call_until_true(
+            check_state, timeout, interval):
+            msg = ("Timed out waiting for node %s to reach %s state(s) %s" %
+                   (node_id, state_attr, target_states))
+            raise exceptions.TimeoutException(msg)
+
+    def wait_provisioning_state(self, node_id, state, timeout):
+        self._node_state_timeout(
+            node_id=node_id, state_attr='provision_state',
+            target_states=state, timeout=timeout)
+
+    def wait_power_state(self, node_id, state):
+        self._node_state_timeout(
+            node_id=node_id, state_attr='power_state',
+            target_states=state, timeout=CONF.baremetal.power_timeout)
+
+    def wait_node(self, instance_id):
+        """Waits for a node to be associated with instance_id."""
+        def _get_node():
+            node = None
+            try:
+                node = self.get_node(instance_id=instance_id)
+            except ironic_exceptions.HTTPNotFound:
+                pass
+            return node is not None
+
+        if not tempest.test.call_until_true(
+            _get_node, CONF.baremetal.association_timeout, 1):
+            msg = ('Timed out waiting to get Ironic node by instance id %s'
+                   % instance_id)
+            raise exceptions.TimeoutException(msg)
+
+    def get_node(self, node_id=None, instance_id=None):
+        if node_id:
+            return self.baremetal_client.node.get(node_id)
+        elif instance_id:
+            return self.baremetal_client.node.get_by_instance_uuid(instance_id)
+
+    def get_ports(self, node_id):
+        ports = []
+        for port in self.baremetal_client.node.list_ports(node_id):
+            ports.append(self.baremetal_client.port.get(port.uuid))
+        return ports
+
+
 class NetworkScenarioTest(OfficialClientTest):
     """
     Base class for network scenario tests
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
new file mode 100644
index 0000000..c53aa83
--- /dev/null
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -0,0 +1,147 @@
+#
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+#
+# 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 import config
+from tempest.openstack.common import log as logging
+from tempest.scenario import manager
+from tempest import test
+
+CONF = config.CONF
+
+LOG = logging.getLogger(__name__)
+
+
+# power/provision states as of icehouse
+class PowerStates(object):
+    """Possible power states of an Ironic node."""
+    POWER_ON = 'power on'
+    POWER_OFF = 'power off'
+    REBOOT = 'rebooting'
+    SUSPEND = 'suspended'
+
+
+class ProvisionStates(object):
+    """Possible provision states of an Ironic node."""
+    NOSTATE = None
+    INIT = 'initializing'
+    ACTIVE = 'active'
+    BUILDING = 'building'
+    DEPLOYWAIT = 'wait call-back'
+    DEPLOYING = 'deploying'
+    DEPLOYFAIL = 'deploy failed'
+    DEPLOYDONE = 'deploy complete'
+    DELETING = 'deleting'
+    DELETED = 'deleted'
+    ERROR = 'error'
+
+
+class BaremetalBasicOptsPXESSH(manager.BaremetalScenarioTest):
+    """
+    This smoke test tests the pxe_ssh Ironic driver.  It follows this basic
+    set of operations:
+        * Creates a keypair
+        * Boots an instance using the keypair
+        * Monitors the associated Ironic node for power and
+          expected state transitions
+        * Validates Ironic node's driver_info has been properly
+          updated
+        * Validates Ironic node's port data has been properly updated
+        * Verifies SSH connectivity using created keypair via fixed IP
+        * Associates a floating ip
+        * Verifies SSH connectivity using created keypair via floating IP
+        * Deletes instance
+        * Monitors the associated Ironic node for power and
+          expected state transitions
+    """
+    def add_keypair(self):
+        self.keypair = self.create_keypair()
+
+    def add_floating_ip(self):
+        floating_ip = self.compute_client.floating_ips.create()
+        self.instance.add_floating_ip(floating_ip)
+        return floating_ip.ip
+
+    def verify_connectivity(self, ip=None):
+        if ip:
+            dest = self.get_remote_client(ip)
+        else:
+            dest = self.get_remote_client(self.instance)
+        dest.validate_authentication()
+
+    def validate_driver_info(self):
+        f_id = self.instance.flavor['id']
+        flavor_extra = self.compute_client.flavors.get(f_id).get_keys()
+        driver_info = self.node.driver_info
+        self.assertEqual(driver_info['pxe_deploy_kernel'],
+                         flavor_extra['baremetal:deploy_kernel_id'])
+        self.assertEqual(driver_info['pxe_deploy_ramdisk'],
+                         flavor_extra['baremetal:deploy_ramdisk_id'])
+        self.assertEqual(driver_info['pxe_image_source'],
+                         self.instance.image['id'])
+
+    def validate_ports(self):
+        for port in self.get_ports(self.node.uuid):
+            n_port_id = port.extra['vif_port_id']
+            n_port = self.network_client.show_port(n_port_id)['port']
+            self.assertEqual(n_port['device_id'], self.instance.id)
+            self.assertEqual(n_port['mac_address'], port.address)
+
+    def boot_instance(self):
+        create_kwargs = {
+            'key_name': self.keypair.id
+        }
+        self.instance = self.create_server(
+            wait=False, create_kwargs=create_kwargs)
+
+        self.set_resource('instance', self.instance)
+
+        self.wait_node(self.instance.id)
+        self.node = self.get_node(instance_id=self.instance.id)
+
+        self.wait_power_state(self.node.uuid, PowerStates.POWER_ON)
+
+        self.wait_provisioning_state(
+            self.node.uuid,
+            [ProvisionStates.DEPLOYWAIT, ProvisionStates.ACTIVE],
+            timeout=15)
+
+        self.wait_provisioning_state(self.node.uuid, ProvisionStates.ACTIVE,
+                                     timeout=CONF.baremetal.active_timeout)
+
+        self.status_timeout(
+            self.compute_client.servers, self.instance.id, 'ACTIVE')
+
+        self.node = self.get_node(instance_id=self.instance.id)
+        self.instance = self.compute_client.servers.get(self.instance.id)
+
+    def terminate_instance(self):
+        self.instance.delete()
+        self.remove_resource('instance')
+        self.wait_power_state(self.node.uuid, PowerStates.POWER_OFF)
+        self.wait_provisioning_state(
+            self.node.uuid,
+            ProvisionStates.NOSTATE,
+            timeout=CONF.baremetal.unprovision_timeout)
+
+    @test.services('baremetal', 'compute', 'image', 'network')
+    def test_baremetal_server_ops(self):
+        self.add_keypair()
+        self.boot_instance()
+        self.validate_driver_info()
+        self.validate_ports()
+        self.verify_connectivity()
+        floating_ip = self.add_floating_ip()
+        self.verify_connectivity(ip=floating_ip)
+        self.terminate_instance()
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index eefc624..13e00a5 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -104,10 +104,11 @@
             instance.add_floating_ip(floating_ip)
             # Check ssh
             try:
-                self.get_remote_client(
+                linux_client = self.get_remote_client(
                     server_or_ip=floating_ip.ip,
                     username=self.image_utils.ssh_user(self.image_ref),
-                    private_key=self.keypair.private)
+                    private_key=self.keypair.private_key)
+                linux_client.validate_authentication()
             except Exception:
                 LOG.exception('ssh to server failed')
                 self._log_console_output()
diff --git a/tempest/services/compute/json/aggregates_client.py b/tempest/services/compute/json/aggregates_client.py
index 68dce39..fe67102 100644
--- a/tempest/services/compute/json/aggregates_client.py
+++ b/tempest/services/compute/json/aggregates_client.py
@@ -105,4 +105,5 @@
         resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
                                post_body)
         body = json.loads(body)
+        self.validate_response(schema.aggregate_set_metadata, resp, body)
         return resp, body['aggregate']
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index bc4a64f..0206b82 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -18,6 +18,7 @@
 
 from tempest.api_schema.compute import flavors as common_schema
 from tempest.api_schema.compute import flavors_access as schema_access
+from tempest.api_schema.compute.v2 import flavors as v2schema
 from tempest.common import rest_client
 from tempest import config
 
@@ -47,6 +48,7 @@
 
         resp, body = self.get(url)
         body = json.loads(body)
+        self.validate_response(v2schema.list_flavors_details, resp, body)
         return resp, body['flavors']
 
     def get_flavor_details(self, flavor_id):
@@ -128,7 +130,8 @@
         """Gets flavor access information given the flavor id."""
         resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id)
         body = json.loads(body)
-        self.validate_response(schema_access.list_flavor_access, resp, body)
+        self.validate_response(schema_access.add_remove_list_flavor_access,
+                               resp, body)
         return resp, body['flavor_access']
 
     def add_flavor_access(self, flavor_id, tenant_id):
@@ -141,6 +144,8 @@
         post_body = json.dumps(post_body)
         resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
         body = json.loads(body)
+        self.validate_response(schema_access.add_remove_list_flavor_access,
+                               resp, body)
         return resp, body['flavor_access']
 
     def remove_flavor_access(self, flavor_id, tenant_id):
@@ -153,4 +158,6 @@
         post_body = json.dumps(post_body)
         resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
         body = json.loads(body)
+        self.validate_response(schema_access.add_remove_list_flavor_access,
+                               resp, body)
         return resp, body['flavor_access']
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 2d23b03..9b09a13 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -262,10 +262,6 @@
         """Reverts a server back to its original flavor."""
         return self.action(server_id, 'revertResize', None, **kwargs)
 
-    def create_image(self, server_id, name):
-        """Creates an image of the given server."""
-        return self.action(server_id, 'createImage', None, name=name)
-
     def list_server_metadata(self, server_id):
         resp, body = self.get("servers/%s/metadata" % str(server_id))
         body = json.loads(body)
diff --git a/tempest/services/compute/v3/json/aggregates_client.py b/tempest/services/compute/v3/json/aggregates_client.py
index 1859642..8d7440e 100644
--- a/tempest/services/compute/v3/json/aggregates_client.py
+++ b/tempest/services/compute/v3/json/aggregates_client.py
@@ -105,4 +105,5 @@
         resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
                                post_body)
         body = json.loads(body)
+        self.validate_response(schema.aggregate_set_metadata, resp, body)
         return resp, body['aggregate']
diff --git a/tempest/services/compute/v3/json/flavors_client.py b/tempest/services/compute/v3/json/flavors_client.py
index 3fdb3ca..189fe3f 100644
--- a/tempest/services/compute/v3/json/flavors_client.py
+++ b/tempest/services/compute/v3/json/flavors_client.py
@@ -18,6 +18,7 @@
 
 from tempest.api_schema.compute import flavors as common_schema
 from tempest.api_schema.compute import flavors_access as schema_access
+from tempest.api_schema.compute.v3 import flavors as v3schema
 from tempest.common import rest_client
 from tempest import config
 
@@ -47,6 +48,7 @@
 
         resp, body = self.get(url)
         body = json.loads(body)
+        self.validate_response(v3schema.list_flavors_details, resp, body)
         return resp, body['flavors']
 
     def get_flavor_details(self, flavor_id):
@@ -128,7 +130,8 @@
         """Gets flavor access information given the flavor id."""
         resp, body = self.get('flavors/%s/flavor-access' % flavor_id)
         body = json.loads(body)
-        self.validate_response(schema_access.list_flavor_access, resp, body)
+        self.validate_response(schema_access.add_remove_list_flavor_access,
+                               resp, body)
         return resp, body['flavor_access']
 
     def add_flavor_access(self, flavor_id, tenant_id):
@@ -141,6 +144,8 @@
         post_body = json.dumps(post_body)
         resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
         body = json.loads(body)
+        self.validate_response(schema_access.add_remove_list_flavor_access,
+                               resp, body)
         return resp, body['flavor_access']
 
     def remove_flavor_access(self, flavor_id, tenant_id):
@@ -153,4 +158,6 @@
         post_body = json.dumps(post_body)
         resp, body = self.post('flavors/%s/action' % flavor_id, post_body)
         body = json.loads(body)
+        self.validate_response(schema_access.add_remove_list_flavor_access,
+                               resp, body)
         return resp, body['flavor_access']
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index fe176bf..55239f7 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -275,13 +275,20 @@
 
         return resp, body['access']
 
-    def request(self, method, url, headers=None, body=None):
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None):
         """A simple HTTP request interface."""
         if headers is None:
             # Always accept 'json', for TokenClientXML too.
             # Because XML response is not easily
             # converted to the corresponding JSON one
             headers = self.get_headers(accept_type="json")
+        elif extra_headers:
+            try:
+                headers.update(self.get_headers(accept_type="json"))
+            except (ValueError, TypeError):
+                headers = self.get_headers(accept_type="json")
+
         resp, resp_body = self.http_obj.request(url, method,
                                                 headers=headers, body=body)
         self._log_request(method, url, resp)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 4b530f1..6829333 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -163,6 +163,12 @@
         body = json.loads(body)
         return resp, body['role']
 
+    def list_roles(self):
+        """Get the list of Roles."""
+        resp, body = self.get("roles")
+        body = json.loads(body)
+        return resp, body['roles']
+
     def update_role(self, name, role_id):
         """Create a Role."""
         post_body = {
@@ -515,13 +521,20 @@
         resp, body = self.post(self.auth_url, body=body)
         return resp, body
 
-    def request(self, method, url, headers=None, body=None):
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None):
         """A simple HTTP request interface."""
         if headers is None:
             # Always accept 'json', for xml token client too.
             # Because XML response is not easily
             # converted to the corresponding JSON one
             headers = self.get_headers(accept_type="json")
+        elif extra_headers:
+            try:
+                headers.update(self.get_headers(accept_type="json"))
+            except (ValueError, TypeError):
+                headers = self.get_headers(accept_type="json")
+
         resp, resp_body = self.http_obj.request(url, method,
                                                 headers=headers, body=body)
         self._log_request(method, url, resp)
diff --git a/tempest/services/identity/v3/xml/endpoints_client.py b/tempest/services/identity/v3/xml/endpoints_client.py
index 93dc3dc..6490e34 100644
--- a/tempest/services/identity/v3/xml/endpoints_client.py
+++ b/tempest/services/identity/v3/xml/endpoints_client.py
@@ -46,12 +46,19 @@
         json = common.xml_to_json(body)
         return json
 
-    def request(self, method, url, headers=None, body=None, wait=None):
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None, wait=None):
         """Overriding the existing HTTP request in super class RestClient."""
+        if extra_headers:
+            try:
+                headers.update(self.get_headers())
+            except (ValueError, TypeError):
+                headers = self.get_headers()
         dscv = CONF.identity.disable_ssl_certificate_validation
         self.http_obj = http.ClosingHttp(
             disable_ssl_certificate_validation=dscv)
         return super(EndPointClientXML, self).request(method, url,
+                                                      extra_headers,
                                                       headers=headers,
                                                       body=body)
 
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index c49f361..35295d7 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -217,6 +217,12 @@
         body = self._parse_body(etree.fromstring(body))
         return resp, body
 
+    def list_roles(self):
+        """Get the list of Roles."""
+        resp, body = self.get("roles")
+        body = self._parse_roles(etree.fromstring(body))
+        return resp, body
+
     def update_role(self, name, role_id):
         """Updates a Role."""
         post_body = common.Element("role",
@@ -516,13 +522,19 @@
         resp, body = self.post(self.auth_url, body=str(common.Document(auth)))
         return resp, body
 
-    def request(self, method, url, headers=None, body=None):
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None):
         """A simple HTTP request interface."""
         if headers is None:
             # Always accept 'json', for xml token client too.
             # Because XML response is not easily
             # converted to the corresponding JSON one
             headers = self.get_headers(accept_type="json")
+        elif extra_headers:
+            try:
+                headers.update(self.get_headers(accept_type="json"))
+            except (ValueError, TypeError):
+                headers = self.get_headers(accept_type="json")
         resp, resp_body = self.http_obj.request(url, method,
                                                 headers=headers, body=body)
         self._log_request(method, url, resp)
diff --git a/tempest/services/identity/v3/xml/policy_client.py b/tempest/services/identity/v3/xml/policy_client.py
index e903089..73d831b 100644
--- a/tempest/services/identity/v3/xml/policy_client.py
+++ b/tempest/services/identity/v3/xml/policy_client.py
@@ -46,12 +46,19 @@
         json = common.xml_to_json(body)
         return json
 
-    def request(self, method, url, headers=None, body=None, wait=None):
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None, wait=None):
         """Overriding the existing HTTP request in super class RestClient."""
+        if extra_headers:
+            try:
+                headers.update(self.get_headers())
+            except (ValueError, TypeError):
+                headers = self.get_headers()
         dscv = CONF.identity.disable_ssl_certificate_validation
         self.http_obj = http.ClosingHttp(
             disable_ssl_certificate_validation=dscv)
         return super(PolicyClientXML, self).request(method, url,
+                                                    extra_headers,
                                                     headers=headers,
                                                     body=body)
 
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index 0945b09..a9d4880 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -24,7 +24,7 @@
     # list of plurals used for xml serialization
     PLURALS = ['dns_nameservers', 'host_routes', 'allocation_pools',
                'fixed_ips', 'extensions', 'extra_dhcp_opts', 'pools',
-               'health_monitors', 'vips']
+               'health_monitors', 'vips', 'members']
 
     def get_rest_client(self, auth_provider):
         rc = rest_client.RestClient(auth_provider)
diff --git a/tempest/services/object_storage/account_client.py b/tempest/services/object_storage/account_client.py
index 6e7910e..a0506f2 100644
--- a/tempest/services/object_storage/account_client.py
+++ b/tempest/services/object_storage/account_client.py
@@ -162,11 +162,17 @@
         self.service = CONF.object_storage.catalog_type
         self.format = 'json'
 
-    def request(self, method, url, headers=None, body=None):
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None):
         """A simple HTTP request interface."""
         self.http_obj = http.ClosingHttp()
         if headers is None:
             headers = {}
+        elif extra_headers:
+            try:
+                headers.update(self.get_headers())
+            except (ValueError, TypeError):
+                headers = {}
 
         # Authorize the request
         req_url, req_headers, req_body = self.auth_provider.auth_request(
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 49f7f49..53a3325 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -29,12 +29,16 @@
 
         self.service = CONF.object_storage.catalog_type
 
-    def create_object(self, container, object_name, data, params=None):
+    def create_object(self, container, object_name, data,
+                      params=None, metadata=None):
         """Create storage object."""
 
         headers = self.get_headers()
         if not data:
             headers['content-length'] = '0'
+        if metadata:
+            for key in metadata:
+                headers[str(key)] = metadata[key]
         url = "%s/%s" % (str(container), str(object_name))
         if params:
             url += '?%s' % urllib.urlencode(params)
@@ -146,13 +150,19 @@
         self.service = CONF.object_storage.catalog_type
         self.format = 'json'
 
-    def request(self, method, url, headers=None, body=None):
+    def request(self, method, url, extra_headers=False, headers=None,
+                body=None):
         """A simple HTTP request interface."""
         dscv = CONF.identity.disable_ssl_certificate_validation
         self.http_obj = http.ClosingHttp(
             disable_ssl_certificate_validation=dscv)
         if headers is None:
             headers = {}
+        elif extra_headers:
+            try:
+                headers.update(self.get_headers())
+            except (ValueError, TypeError):
+                headers = {}
 
         # Authorize the request
         req_url, req_headers, req_body = self.auth_provider.auth_request(
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index 113003c..2311bdd 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -154,7 +154,8 @@
                 if resource_status == status:
                     return
                 if fail_regexp.search(resource_status):
-                    raise exceptions.StackBuildErrorException(
+                    raise exceptions.StackResourceBuildErrorException(
+                        resource_name=resource_name,
                         stack_identifier=stack_identifier,
                         resource_status=resource_status,
                         resource_status_reason=body['resource_status_reason'])
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index e4d2e8d..b55a037 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -67,10 +67,10 @@
         body = json.loads(body)
         return resp, body['volume']
 
-    def create_volume(self, size, **kwargs):
+    def create_volume(self, size=None, **kwargs):
         """
         Creates a new Volume.
-        size(Required): Size of volume in GB.
+        size: Size of volume in GB.
         Following optional keyword arguments are accepted:
         display_name: Optional Volume Name.
         metadata: A dictionary of values to be used as metadata.
@@ -78,6 +78,10 @@
         snapshot_id: When specified the volume is created from this snapshot
         imageRef: When specified the volume is created from this image
         """
+        # for bug #1293885:
+        # If no size specified, read volume size from CONF
+        if size is None:
+            size = CONF.volume.volume_size
         post_body = {'size': size}
         post_body.update(kwargs)
         post_body = json.dumps({'volume': post_body})
diff --git a/tempest/services/volume/v2/json/volumes_client.py b/tempest/services/volume/v2/json/volumes_client.py
index 5bfa75f..df20a2a 100644
--- a/tempest/services/volume/v2/json/volumes_client.py
+++ b/tempest/services/volume/v2/json/volumes_client.py
@@ -68,10 +68,10 @@
         body = json.loads(body)
         return resp, body['volume']
 
-    def create_volume(self, size, **kwargs):
+    def create_volume(self, size=None, **kwargs):
         """
         Creates a new Volume.
-        size(Required): Size of volume in GB.
+        size: Size of volume in GB.
         Following optional keyword arguments are accepted:
         name: Optional Volume Name.
         metadata: A dictionary of values to be used as metadata.
@@ -79,6 +79,10 @@
         snapshot_id: When specified the volume is created from this snapshot
         imageRef: When specified the volume is created from this image
         """
+        # for bug #1293885:
+        # If no size specified, read volume size from CONF
+        if size is None:
+            size = CONF.volume.volume_size
         post_body = {'size': size}
         post_body.update(kwargs)
         post_body = json.dumps({'volume': post_body})
diff --git a/tempest/services/volume/v2/xml/volumes_client.py b/tempest/services/volume/v2/xml/volumes_client.py
index e735a65..1fdaf19 100644
--- a/tempest/services/volume/v2/xml/volumes_client.py
+++ b/tempest/services/volume/v2/xml/volumes_client.py
@@ -117,10 +117,10 @@
         body = self._check_if_bootable(body)
         return resp, body
 
-    def create_volume(self, size, **kwargs):
+    def create_volume(self, size=None, **kwargs):
         """Creates a new Volume.
 
-        :param size: Size of volume in GB. (Required)
+        :param size: Size of volume in GB.
         :param name: Optional Volume Name.
         :param metadata: An optional dictionary of values for metadata.
         :param volume_type: Optional Name of volume_type for the volume
@@ -129,6 +129,10 @@
         :param imageRef: When specified the volume is created from this
                          image
         """
+        # for bug #1293885:
+        # If no size specified, read volume size from CONF
+        if size is None:
+            size = CONF.volume.volume_size
         # NOTE(afazekas): it should use a volume namespace
         volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
 
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 6866dad..65bc321 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -118,10 +118,10 @@
         body = self._check_if_bootable(body)
         return resp, body
 
-    def create_volume(self, size, **kwargs):
+    def create_volume(self, size=None, **kwargs):
         """Creates a new Volume.
 
-        :param size: Size of volume in GB. (Required)
+        :param size: Size of volume in GB.
         :param display_name: Optional Volume Name.
         :param metadata: An optional dictionary of values for metadata.
         :param volume_type: Optional Name of volume_type for the volume
@@ -130,6 +130,10 @@
         :param imageRef: When specified the volume is created from this
                          image
         """
+        # for bug #1293885:
+        # If no size specified, read volume size from CONF
+        if size is None:
+            size = CONF.volume.volume_size
         # NOTE(afazekas): it should use a volume namespace
         volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
 
diff --git a/tempest/test.py b/tempest/test.py
index e4019f9..8df405c 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -94,6 +94,7 @@
     service_list = {
         'compute': CONF.service_available.nova,
         'image': CONF.service_available.glance,
+        'baremetal': CONF.service_available.ironic,
         'volume': CONF.service_available.cinder,
         'orchestration': CONF.service_available.heat,
         # NOTE(mtreinish) nova-network will provide networking functionality
diff --git a/tempest/tests/test_commands.py b/tempest/tests/test_commands.py
new file mode 100644
index 0000000..bdb9269
--- /dev/null
+++ b/tempest/tests/test_commands.py
@@ -0,0 +1,87 @@
+# Copyright 2014 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 mock
+import subprocess
+
+from tempest.common import commands
+from tempest.tests import base
+
+
+class TestCommands(base.TestCase):
+
+    def setUp(self):
+        super(TestCommands, self).setUp()
+        self.subprocess_args = {'stdout': subprocess.PIPE,
+                                'stderr': subprocess.STDOUT}
+
+    @mock.patch('subprocess.Popen')
+    def test_ip_addr_raw(self, mock):
+        expected = ['/usr/bin/sudo', '-n', 'ip', 'a']
+        commands.ip_addr_raw()
+        mock.assert_called_once_with(expected, **self.subprocess_args)
+
+    @mock.patch('subprocess.Popen')
+    def test_ip_route_raw(self, mock):
+        expected = ['/usr/bin/sudo', '-n', 'ip', 'r']
+        commands.ip_route_raw()
+        mock.assert_called_once_with(expected, **self.subprocess_args)
+
+    @mock.patch('subprocess.Popen')
+    def test_ip_ns_raw(self, mock):
+        expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'list']
+        commands.ip_ns_raw()
+        mock.assert_called_once_with(expected, **self.subprocess_args)
+
+    @mock.patch('subprocess.Popen')
+    def test_iptables_raw(self, mock):
+        table = 'filter'
+        expected = ['/usr/bin/sudo', '-n', 'iptables', '-v', '-S', '-t',
+                    '%s' % table]
+        commands.iptables_raw(table)
+        mock.assert_called_once_with(expected, **self.subprocess_args)
+
+    @mock.patch('subprocess.Popen')
+    def test_ip_ns_list(self, mock):
+        expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'list']
+        commands.ip_ns_list()
+        mock.assert_called_once_with(expected, **self.subprocess_args)
+
+    @mock.patch('subprocess.Popen')
+    def test_ip_ns_addr(self, mock):
+        ns_list = commands.ip_ns_list()
+        for ns in ns_list:
+            expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'exec', ns,
+                        'ip', 'a']
+            commands.ip_ns_addr(ns)
+            mock.assert_called_once_with(expected, **self.subprocess_args)
+
+    @mock.patch('subprocess.Popen')
+    def test_ip_ns_route(self, mock):
+        ns_list = commands.ip_ns_list()
+        for ns in ns_list:
+            expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'exec', ns,
+                        'ip', 'r']
+            commands.ip_ns_route(ns)
+            mock.assert_called_once_with(expected, **self.subprocess_args)
+
+    @mock.patch('subprocess.Popen')
+    def test_iptables_ns(self, mock):
+        table = 'filter'
+        ns_list = commands.ip_ns_list()
+        for ns in ns_list:
+            expected = ['/usr/bin/sudo', '-n', 'ip', 'netns', 'exec', ns,
+                        'iptables', '-v', '-S', '-t', table]
+            commands.iptables_ns(ns, table)
+            mock.assert_called_once_with(expected, **self.subprocess_args)
diff --git a/tempest/tests/test_rest_client.py b/tempest/tests/test_rest_client.py
index cfbb37d..64ad3bc 100644
--- a/tempest/tests/test_rest_client.py
+++ b/tempest/tests/test_rest_client.py
@@ -139,6 +139,102 @@
         self._verify_headers(resp)
 
 
+class TestRestClientUpdateHeaders(BaseRestClientTestClass):
+    def setUp(self):
+        self.fake_http = fake_http.fake_httplib2()
+        super(TestRestClientUpdateHeaders, self).setUp()
+        self.useFixture(mockpatch.PatchObject(self.rest_client,
+                                              '_error_checker'))
+        self.headers = {'X-Configuration-Session': 'session_id'}
+
+    def test_post_update_headers(self):
+        __, return_dict = self.rest_client.post(self.url, {},
+                                                extra_headers=True,
+                                                headers=self.headers)
+
+        self.assertDictContainsSubset(
+            {'X-Configuration-Session': 'session_id',
+             'Content-Type': 'application/json',
+             'Accept': 'application/json'},
+            return_dict['headers']
+        )
+
+    def test_get_update_headers(self):
+        __, return_dict = self.rest_client.get(self.url,
+                                               extra_headers=True,
+                                               headers=self.headers)
+
+        self.assertDictContainsSubset(
+            {'X-Configuration-Session': 'session_id',
+             'Content-Type': 'application/json',
+             'Accept': 'application/json'},
+            return_dict['headers']
+        )
+
+    def test_delete_update_headers(self):
+        __, return_dict = self.rest_client.delete(self.url,
+                                                  extra_headers=True,
+                                                  headers=self.headers)
+
+        self.assertDictContainsSubset(
+            {'X-Configuration-Session': 'session_id',
+             'Content-Type': 'application/json',
+             'Accept': 'application/json'},
+            return_dict['headers']
+        )
+
+    def test_patch_update_headers(self):
+        __, return_dict = self.rest_client.patch(self.url, {},
+                                                 extra_headers=True,
+                                                 headers=self.headers)
+
+        self.assertDictContainsSubset(
+            {'X-Configuration-Session': 'session_id',
+             'Content-Type': 'application/json',
+             'Accept': 'application/json'},
+            return_dict['headers']
+        )
+
+    def test_put_update_headers(self):
+        __, return_dict = self.rest_client.put(self.url, {},
+                                               extra_headers=True,
+                                               headers=self.headers)
+
+        self.assertDictContainsSubset(
+            {'X-Configuration-Session': 'session_id',
+             'Content-Type': 'application/json',
+             'Accept': 'application/json'},
+            return_dict['headers']
+        )
+
+    def test_head_update_headers(self):
+        self.useFixture(mockpatch.PatchObject(self.rest_client,
+                                              'response_checker'))
+
+        __, return_dict = self.rest_client.head(self.url,
+                                                extra_headers=True,
+                                                headers=self.headers)
+
+        self.assertDictContainsSubset(
+            {'X-Configuration-Session': 'session_id',
+             'Content-Type': 'application/json',
+             'Accept': 'application/json'},
+            return_dict['headers']
+        )
+
+    def test_copy_update_headers(self):
+        __, return_dict = self.rest_client.copy(self.url,
+                                                extra_headers=True,
+                                                headers=self.headers)
+
+        self.assertDictContainsSubset(
+            {'X-Configuration-Session': 'session_id',
+             'Content-Type': 'application/json',
+             'Accept': 'application/json'},
+            return_dict['headers']
+        )
+
+
 class TestRestClientHeadersXML(TestRestClientHeadersJSON):
     TYPE = "xml"
 
diff --git a/tools/check_logs.py b/tools/check_logs.py
index b5b1780..bc4eaca 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -46,7 +46,6 @@
     'n-api',
     'n-cpu',
     'n-net',
-    'n-sch',
     'q-agt',
     'q-dhcp',
     'q-lbaas',
diff --git a/tools/verify_tempest_config.py b/tools/verify_tempest_config.py
index aa92c0b..30785c4 100755
--- a/tools/verify_tempest_config.py
+++ b/tools/verify_tempest_config.py
@@ -16,6 +16,7 @@
 
 import json
 import sys
+import urlparse
 
 import httplib2
 
@@ -39,19 +40,37 @@
             not CONF.image_feature_enabled.api_v2))
 
 
-def verify_nova_api_versions(os):
-    # Check nova api versions - only get base URL without PATH
-    os.servers_client.skip_path = True
-    # The nova base endpoint url includes the version but to get the versions
-    # list the unversioned endpoint is needed
-    v2_endpoint = os.servers_client.base_url
-    v2_endpoint_parts = v2_endpoint.split('/')
-    endpoint = v2_endpoint_parts[0] + '//' + v2_endpoint_parts[2]
+def _get_api_versions(os, service):
+    client_dict = {
+        'nova': os.servers_client,
+        'keystone': os.identity_client,
+    }
+    client_dict[service].skip_path()
+    endpoint_parts = urlparse.urlparse(client_dict[service])
+    endpoint = endpoint_parts.scheme + '//' + endpoint_parts.netloc
     __, body = RAW_HTTP.request(endpoint, 'GET')
+    client_dict[service].reset_path()
     body = json.loads(body)
-    # Restore full base_url
-    os.servers_client.skip_path = False
-    versions = map(lambda x: x['id'], body['versions'])
+    if service == 'keystone':
+        versions = map(lambda x: x['id'], body['versions']['values'])
+    else:
+        versions = map(lambda x: x['id'], body['versions'])
+    return versions
+
+
+def verify_keystone_api_versions(os):
+    # Check keystone api versions
+    versions = _get_api_versions(os, 'keystone')
+    if CONF.identity_feature_enabled.api_v2 != ('v2.0' in versions):
+        print('Config option identity api_v2 should be change to %s' % (
+            not CONF.identity_feature_enabled.api_v2))
+    if CONF.identity_feature_enabled.api_v3 != ('v3.0' in versions):
+        print('Config option identity api_v3 should be change to %s' % (
+            not CONF.identity_feature_enabled.api_v3))
+
+
+def verify_nova_api_versions(os):
+    versions = _get_api_versions(os, 'nova')
     if CONF.compute_feature_enabled.api_v3 != ('v3.0' in versions):
         print('Config option compute api_v3 should be change to: %s' % (
               not CONF.compute_feature_enabled.api_v3))
@@ -197,6 +216,7 @@
         elif service not in services:
             continue
         results = verify_extensions(os, service, results)
+    verify_keystone_api_versions(os)
     verify_glance_api_versions(os)
     verify_nova_api_versions(os)
     display_results(results)