Merge "Validate list_keypair attribute of Nova V2/V3 APIs"
diff --git a/requirements.txt b/requirements.txt
index 434e12e..a18b092 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -13,7 +13,7 @@
 python-neutronclient>=2.3.4,<3
 python-cinderclient>=1.0.6
 python-heatclient>=0.2.3
-python-savannaclient>=0.5.0
+python-saharaclient>=0.6.0
 python-swiftclient>=1.6
 testresources>=0.2.4
 keyring>=1.6.1,<2.0,>=2.1
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 05b763a..759585e 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -170,10 +170,12 @@
                 flag = True
         self.assertTrue(flag)
 
+    @test.skip_because(bug='1286297')
     @test.attr(type='gate')
     def test_list_non_public_flavor(self):
-        # Create a flavor with os-flavor-access:is_public false should
-        # be present in list_details.
+        # Create a flavor with os-flavor-access:is_public false.
+        # The flavor should not be present in list_details as the
+        # tenant is not automatically added access list.
         # This operation requires the user to have 'admin' role
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
         new_flavor_id = data_utils.rand_int_id(start=1000)
@@ -192,7 +194,7 @@
         for flavor in flavors:
             if flavor['name'] == flavor_name:
                 flag = True
-        self.assertTrue(flag)
+        self.assertFalse(flag)
 
         # Verify flavor is not retrieved with other user
         flag = False
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index 4804ce4..aa0138f 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -46,9 +46,11 @@
         cls.vcpus = 1
         cls.disk = 10
 
+    @test.skip_because(bug='1286297')
     @test.attr(type='gate')
     def test_flavor_access_list_with_private_flavor(self):
-        # Test to list flavor access successfully by querying private flavor
+        # Test to make sure that list flavor access on a newly created
+        # private flavor will return an empty access list
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
         new_flavor_id = data_utils.rand_int_id(start=1000)
         resp, new_flavor = self.client.create_flavor(flavor_name,
@@ -60,10 +62,7 @@
         self.assertEqual(resp.status, 200)
         resp, flavor_access = self.client.list_flavor_access(new_flavor_id)
         self.assertEqual(resp.status, 200)
-        self.assertEqual(len(flavor_access), 1, str(flavor_access))
-        first_flavor = flavor_access[0]
-        self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
-        self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
+        self.assertEqual(len(flavor_access), 0, str(flavor_access))
 
     @test.attr(type='gate')
     def test_flavor_access_add_remove(self):
diff --git a/tempest/api/compute/admin/test_flavors_negative.py b/tempest/api/compute/admin/test_flavors_negative.py
index b882ff4..162e419 100644
--- a/tempest/api/compute/admin/test_flavors_negative.py
+++ b/tempest/api/compute/admin/test_flavors_negative.py
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testscenarios
 import uuid
 
 from tempest.api.compute import base
@@ -21,7 +20,7 @@
 from tempest import exceptions
 from tempest import test
 
-load_tests = testscenarios.load_tests_apply_scenarios
+load_tests = test.NegativeAutoTest.load_tests
 
 
 class FlavorsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -108,8 +107,6 @@
     _service = 'compute'
     _schema_file = 'compute/admin/flavor_create.json'
 
-    scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
     @test.attr(type=['negative', 'gate'])
     def test_create_flavor(self):
         # flavor details are not returned for non-existent flavors
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index 4ba5023..a81b7d9 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -13,13 +13,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testscenarios
 
 from tempest.api.compute import base
 from tempest import test
 
 
-load_tests = testscenarios.load_tests_apply_scenarios
+load_tests = test.NegativeAutoTest.load_tests
 
 
 class FlavorsListNegativeTestJSON(base.BaseV2ComputeTest,
@@ -27,8 +26,6 @@
     _service = 'compute'
     _schema_file = 'compute/flavors/flavors_list.json'
 
-    scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
     @test.attr(type=['negative', 'gate'])
     def test_list_flavors_with_detail(self):
         self.execute(self._schema_file)
@@ -39,8 +36,6 @@
     _service = 'compute'
     _schema_file = 'compute/flavors/flavor_details.json'
 
-    scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
     @classmethod
     def setUpClass(cls):
         super(FlavorDetailsNegativeTestJSON, cls).setUpClass()
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 26a75a2..72ccc71 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -14,7 +14,6 @@
 #    under the License.
 
 import base64
-import time
 
 import testtools
 import urlparse
@@ -222,18 +221,8 @@
         self.client.revert_resize(self.server_id)
         self.client.wait_for_server_status(self.server_id, 'ACTIVE')
 
-        # Need to poll for the id change until lp#924371 is fixed
         resp, server = self.client.get_server(self.server_id)
-        start = int(time.time())
-
-        while server['flavor']['id'] != previous_flavor_ref:
-            time.sleep(self.build_interval)
-            resp, server = self.client.get_server(self.server_id)
-
-            if int(time.time()) - start >= self.build_timeout:
-                message = 'Server %s failed to revert resize within the \
-                required time (%s s).' % (self.server_id, self.build_timeout)
-                raise exceptions.TimeoutException(message)
+        self.assertEqual(previous_flavor_ref, server['flavor']['id'])
 
     @test.attr(type='gate')
     def test_create_backup(self):
diff --git a/tempest/api/compute/servers/test_servers_negative_new.py b/tempest/api/compute/servers/test_servers_negative_new.py
index 42ace76..f860ff9 100644
--- a/tempest/api/compute/servers/test_servers_negative_new.py
+++ b/tempest/api/compute/servers/test_servers_negative_new.py
@@ -13,13 +13,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testscenarios
 
 from tempest.api.compute import base
 from tempest import test
 
 
-load_tests = testscenarios.load_tests_apply_scenarios
+load_tests = test.NegativeAutoTest.load_tests
 
 
 class GetConsoleOutputNegativeTestJSON(base.BaseV2ComputeTest,
@@ -27,8 +26,6 @@
     _service = 'compute'
     _schema_file = 'compute/servers/get_console_output.json'
 
-    scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
     @classmethod
     def setUpClass(cls):
         super(GetConsoleOutputNegativeTestJSON, cls).setUpClass()
diff --git a/tempest/api/compute/v3/flavors/test_flavors_negative.py b/tempest/api/compute/v3/flavors/test_flavors_negative.py
index 346f6d6..1c0e4fb 100644
--- a/tempest/api/compute/v3/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/v3/flavors/test_flavors_negative.py
@@ -13,13 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testscenarios
-
 from tempest.api.compute import base
 from tempest import test
 
 
-load_tests = testscenarios.load_tests_apply_scenarios
+load_tests = test.NegativeAutoTest.load_tests
 
 
 class FlavorsListNegativeV3Test(base.BaseV3ComputeTest,
@@ -27,8 +25,6 @@
     _service = 'computev3'
     _schema_file = 'compute/flavors/flavors_list_v3.json'
 
-    scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
     @test.attr(type=['negative', 'gate'])
     def test_list_flavors_with_detail(self):
         self.execute(self._schema_file)
@@ -39,8 +35,6 @@
     _service = 'computev3'
     _schema_file = 'compute/flavors/flavor_details_v3.json'
 
-    scenarios = test.NegativeAutoTest.generate_scenario(_schema_file)
-
     @classmethod
     def setUpClass(cls):
         super(FlavorDetailsNegativeV3Test, cls).setUpClass()
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 555d028..2582fa8 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -13,8 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import time
-
 import testtools
 
 from tempest.api.compute import base
@@ -212,18 +210,8 @@
         self.client.revert_resize(self.server_id)
         self.client.wait_for_server_status(self.server_id, 'ACTIVE')
 
-        # Need to poll for the id change until lp#924371 is fixed
         resp, server = self.client.get_server(self.server_id)
-        start = int(time.time())
-
-        while server['flavor']['id'] != previous_flavor_ref:
-            time.sleep(self.build_interval)
-            resp, server = self.client.get_server(self.server_id)
-
-            if int(time.time()) - start >= self.build_timeout:
-                message = 'Server %s failed to revert resize within the \
-                required time (%s s).' % (self.server_id, self.build_timeout)
-                raise exceptions.TimeoutException(message)
+        self.assertEqual(previous_flavor_ref, server['flavor']['id'])
 
     @test.attr(type='gate')
     def test_create_backup(self):
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 3503aa0..f92ad68 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -90,7 +90,7 @@
             cls.client.delete_vpnservice(vpnservice['id'])
         # Clean up floating IPs
         for floating_ip in cls.floating_ips:
-            cls.client.delete_floating_ip(floating_ip['id'])
+            cls.client.delete_floatingip(floating_ip['id'])
         # Clean up routers
         for router in cls.routers:
             resp, body = cls.client.list_router_interfaces(router['id'])
@@ -193,11 +193,10 @@
         return router
 
     @classmethod
-    def create_floating_ip(cls, external_network_id, **kwargs):
+    def create_floatingip(cls, external_network_id):
         """Wrapper utility that returns a test floating IP."""
-        resp, body = cls.client.create_floating_ip(
-            external_network_id,
-            **kwargs)
+        resp, body = cls.client.create_floatingip(
+            floating_network_id=external_network_id)
         fip = body['floatingip']
         cls.floating_ips.append(fip)
         return fip
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index b31c090..06871ad 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -65,8 +65,12 @@
     @test.attr(type='smoke')
     def test_create_list_show_update_delete_floating_ip(self):
         # Creates a floating IP
-        created_floating_ip = self.create_floating_ip(
-            self.ext_net_id, port_id=self.ports[0]['id'])
+        resp, body = self.client.create_floatingip(
+            floating_network_id=self.ext_net_id, port_id=self.ports[0]['id'])
+        self.assertEqual('201', resp['status'])
+        created_floating_ip = body['floatingip']
+        self.addCleanup(self.client.delete_floatingip,
+                        created_floating_ip['id'])
         self.assertIsNotNone(created_floating_ip['id'])
         self.assertIsNotNone(created_floating_ip['tenant_id'])
         self.assertIsNotNone(created_floating_ip['floating_ip_address'])
@@ -74,7 +78,7 @@
         self.assertEqual(created_floating_ip['floating_network_id'],
                          self.ext_net_id)
         # Verifies the details of a floating_ip
-        resp, floating_ip = self.client.show_floating_ip(
+        resp, floating_ip = self.client.show_floatingip(
             created_floating_ip['id'])
         self.assertEqual('200', resp['status'])
         shown_floating_ip = floating_ip['floatingip']
@@ -95,7 +99,7 @@
             floatingip_id_list.append(f['id'])
         self.assertIn(created_floating_ip['id'], floatingip_id_list)
         # Associate floating IP to the other port
-        resp, floating_ip = self.client.update_floating_ip(
+        resp, floating_ip = self.client.update_floatingip(
             created_floating_ip['id'], port_id=self.ports[1]['id'])
         self.assertEqual('200', resp['status'])
         updated_floating_ip = floating_ip['floatingip']
@@ -105,7 +109,7 @@
         self.assertEqual(updated_floating_ip['router_id'], self.router['id'])
 
         # Disassociate floating IP from the port
-        resp, floating_ip = self.client.update_floating_ip(
+        resp, floating_ip = self.client.update_floatingip(
             created_floating_ip['id'], port_id=None)
         self.assertEqual('200', resp['status'])
         updated_floating_ip = floating_ip['floatingip']
@@ -116,17 +120,22 @@
     @test.attr(type='smoke')
     def test_floating_ip_delete_port(self):
         # Create a floating IP
-        created_floating_ip = self.create_floating_ip(self.ext_net_id)
+        resp, body = self.client.create_floatingip(
+            floating_network_id=self.ext_net_id)
+        self.assertEqual('201', resp['status'])
+        created_floating_ip = body['floatingip']
+        self.addCleanup(self.client.delete_floatingip,
+                        created_floating_ip['id'])
         # Create a port
         resp, port = self.client.create_port(network_id=self.network['id'])
         created_port = port['port']
-        resp, floating_ip = self.client.update_floating_ip(
+        resp, floating_ip = self.client.update_floatingip(
             created_floating_ip['id'], port_id=created_port['id'])
         self.assertEqual('200', resp['status'])
         # Delete port
         self.client.delete_port(created_port['id'])
         # Verifies the details of the floating_ip
-        resp, floating_ip = self.client.show_floating_ip(
+        resp, floating_ip = self.client.show_floatingip(
             created_floating_ip['id'])
         self.assertEqual('200', resp['status'])
         shown_floating_ip = floating_ip['floatingip']
@@ -139,8 +148,12 @@
     @test.attr(type='smoke')
     def test_floating_ip_update_different_router(self):
         # Associate a floating IP to a port on a router
-        created_floating_ip = self.create_floating_ip(
-            self.ext_net_id, port_id=self.ports[1]['id'])
+        resp, body = self.client.create_floatingip(
+            floating_network_id=self.ext_net_id, port_id=self.ports[1]['id'])
+        self.assertEqual('201', resp['status'])
+        created_floating_ip = body['floatingip']
+        self.addCleanup(self.client.delete_floatingip,
+                        created_floating_ip['id'])
         self.assertEqual(created_floating_ip['router_id'], self.router['id'])
         network2 = self.create_network()
         subnet2 = self.create_subnet(network2)
@@ -149,7 +162,7 @@
         self.create_router_interface(router2['id'], subnet2['id'])
         port_other_router = self.create_port(network2)
         # Associate floating IP to the other port on another router
-        resp, floating_ip = self.client.update_floating_ip(
+        resp, floating_ip = self.client.update_floatingip(
             created_floating_ip['id'], port_id=port_other_router['id'])
         self.assertEqual('200', resp['status'])
         updated_floating_ip = floating_ip['floatingip']
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index 3424082..9bf9568 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -13,6 +13,7 @@
 from tempest import clients
 from tempest.common.utils import data_utils
 from tempest import config
+from tempest import exceptions
 from tempest.openstack.common import log as logging
 import tempest.test
 
@@ -35,6 +36,7 @@
 
         cls.os = os
         cls.orchestration_client = os.orchestration_client
+        cls.client = os.orchestration_client
         cls.servers_client = os.servers_client
         cls.keypairs_client = os.keypairs_client
         cls.network_client = os.network_client
@@ -72,15 +74,15 @@
     def clear_stacks(cls):
         for stack_identifier in cls.stacks:
             try:
-                cls.orchestration_client.delete_stack(stack_identifier)
-            except Exception:
+                cls.client.delete_stack(stack_identifier)
+            except exceptions.NotFound:
                 pass
 
         for stack_identifier in cls.stacks:
             try:
-                cls.orchestration_client.wait_for_stack_status(
+                cls.client.wait_for_stack_status(
                     stack_identifier, 'DELETE_COMPLETE')
-            except Exception:
+            except exceptions.NotFound:
                 pass
 
     @classmethod
diff --git a/tempest/api/orchestration/stacks/test_limits.py b/tempest/api/orchestration/stacks/test_limits.py
index 22f544d..893dcc4 100644
--- a/tempest/api/orchestration/stacks/test_limits.py
+++ b/tempest/api/orchestration/stacks/test_limits.py
@@ -24,16 +24,10 @@
 
 
 class TestServerStackLimits(base.BaseOrchestrationTest):
-    _interface = 'json'
-
-    @classmethod
-    def setUpClass(cls):
-        super(TestServerStackLimits, cls).setUpClass()
-        cls.client = cls.orchestration_client
-        cls.stack_name = data_utils.rand_name('heat')
 
     @attr(type='gate')
     def test_exceed_max_template_size_fails(self):
+        stack_name = data_utils.rand_name('heat')
         fill = 'A' * CONF.orchestration.max_template_size
         template = '''
 HeatTemplateFormatVersion: '2012-12-12'
@@ -41,5 +35,5 @@
 Outputs:
   Foo: bar''' % fill
         ex = self.assertRaises(exceptions.BadRequest, self.create_stack,
-                               self.stack_name, template)
+                               stack_name, template)
         self.assertIn('Template exceeds maximum allowed size', str(ex))
diff --git a/tempest/api/telemetry/test_telemetry_alarming_api.py b/tempest/api/telemetry/test_telemetry_alarming_api.py
index 907d3d0..a59d3ae 100644
--- a/tempest/api/telemetry/test_telemetry_alarming_api.py
+++ b/tempest/api/telemetry/test_telemetry_alarming_api.py
@@ -20,9 +20,29 @@
 
     @attr(type="gate")
     def test_alarm_list(self):
-        resp, _ = self.telemetry_client.list_alarms()
+        # Create an alarm to verify in the list of alarms
+        created_alarm_ids = list()
+        fetched_ids = list()
+        rules = {'meter_name': 'cpu_util',
+                 'comparison_operator': 'gt',
+                 'threshold': 80.0,
+                 'period': 70}
+        for i in range(3):
+            resp, body = self.create_alarm(threshold_rule=rules)
+            created_alarm_ids.append(body['alarm_id'])
+
+        # List alarms
+        resp, alarm_list = self.telemetry_client.list_alarms()
         self.assertEqual(int(resp['status']), 200)
 
+        # Verify created alarm in the list
+        fetched_ids = [a['alarm_id'] for a in alarm_list]
+        missing_alarms = [a for a in created_alarm_ids if a not in fetched_ids]
+        self.assertEqual(0, len(missing_alarms),
+                         "Failed to find the following created alarm(s)"
+                         " in a fetched list: %s" %
+                         ', '.join(str(a) for a in missing_alarms))
+
     @attr(type="gate")
     def test_create_alarm(self):
         rules = {'meter_name': 'cpu_util',
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 6178a1c..e79d23c 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -25,10 +25,10 @@
     _interface = "json"
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(VolumeMultiBackendTest, cls).setUpClass()
         if not CONF.volume_feature_enabled.multi_backend:
-            cls.tearDownClass()
             raise cls.skipException("Cinder multi-backend feature disabled")
 
         cls.backend1_name = CONF.volume.backend1_name
@@ -37,40 +37,36 @@
         cls.volume_client = cls.os_adm.volumes_client
         cls.volume_type_id_list = []
         cls.volume_id_list = []
-        try:
-            # Volume/Type creation (uses backend1_name)
-            type1_name = data_utils.rand_name('Type-')
-            vol1_name = data_utils.rand_name('Volume-')
-            extra_specs1 = {"volume_backend_name": cls.backend1_name}
-            resp, cls.type1 = cls.client.create_volume_type(
-                type1_name, extra_specs=extra_specs1)
-            cls.volume_type_id_list.append(cls.type1['id'])
 
-            resp, cls.volume1 = cls.volume_client.create_volume(
-                size=1, display_name=vol1_name, volume_type=type1_name)
-            cls.volume_id_list.append(cls.volume1['id'])
-            cls.volume_client.wait_for_volume_status(cls.volume1['id'],
+        # Volume/Type creation (uses backend1_name)
+        type1_name = data_utils.rand_name('Type-')
+        vol1_name = data_utils.rand_name('Volume-')
+        extra_specs1 = {"volume_backend_name": cls.backend1_name}
+        resp, cls.type1 = cls.client.create_volume_type(
+            type1_name, extra_specs=extra_specs1)
+        cls.volume_type_id_list.append(cls.type1['id'])
+
+        resp, cls.volume1 = cls.volume_client.create_volume(
+            size=1, display_name=vol1_name, volume_type=type1_name)
+        cls.volume_id_list.append(cls.volume1['id'])
+        cls.volume_client.wait_for_volume_status(cls.volume1['id'],
+                                                 'available')
+
+        if cls.backend1_name != cls.backend2_name:
+            # Volume/Type creation (uses backend2_name)
+            type2_name = data_utils.rand_name('Type-')
+            vol2_name = data_utils.rand_name('Volume-')
+            extra_specs2 = {"volume_backend_name": cls.backend2_name}
+            resp, cls.type2 = cls.client.create_volume_type(
+                type2_name, extra_specs=extra_specs2)
+            cls.volume_type_id_list.append(cls.type2['id'])
+
+            resp, cls.volume2 = cls.volume_client.create_volume(
+                size=1, display_name=vol2_name, volume_type=type2_name)
+            cls.volume_id_list.append(cls.volume2['id'])
+            cls.volume_client.wait_for_volume_status(cls.volume2['id'],
                                                      'available')
 
-            if cls.backend1_name != cls.backend2_name:
-                # Volume/Type creation (uses backend2_name)
-                type2_name = data_utils.rand_name('Type-')
-                vol2_name = data_utils.rand_name('Volume-')
-                extra_specs2 = {"volume_backend_name": cls.backend2_name}
-                resp, cls.type2 = cls.client.create_volume_type(
-                    type2_name, extra_specs=extra_specs2)
-                cls.volume_type_id_list.append(cls.type2['id'])
-
-                resp, cls.volume2 = cls.volume_client.create_volume(
-                    size=1, display_name=vol2_name, volume_type=type2_name)
-                cls.volume_id_list.append(cls.volume2['id'])
-                cls.volume_client.wait_for_volume_status(cls.volume2['id'],
-                                                         'available')
-        except Exception as e:
-            LOG.exception("setup failed: %s" % e)
-            cls.tearDownClass()
-            raise
-
     @classmethod
     def tearDownClass(cls):
         # volumes deletion
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index cd6d7a8..f9fbe18 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -27,6 +27,7 @@
     _interface = "json"
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(VolumesBackupsTest, cls).setUpClass()
 
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index e94c700..0d57d47 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -23,16 +23,13 @@
     _interface = "json"
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(VolumeMetadataTest, cls).setUpClass()
         # Create a volume
         cls.volume = cls.create_volume()
         cls.volume_id = cls.volume['id']
 
-    @classmethod
-    def tearDownClass(cls):
-        super(VolumeMetadataTest, cls).tearDownClass()
-
     def tearDown(self):
         # Update the metadata to {}
         self.volumes_client.update_volume_metadata(self.volume_id, {})
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index c356342..e2f7a38 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -56,6 +56,7 @@
                              [str_vol(v) for v in fetched_list]))
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(VolumesListTest, cls).setUpClass()
         cls.client = cls.volumes_client
@@ -65,24 +66,10 @@
         cls.volume_id_list = []
         cls.metadata = {'Type': 'work'}
         for i in range(3):
-            try:
-                volume = cls.create_volume(metadata=cls.metadata)
-
-                resp, volume = cls.client.get_volume(volume['id'])
-                cls.volume_list.append(volume)
-                cls.volume_id_list.append(volume['id'])
-            except Exception:
-                LOG.exception('Failed to create volume. %d volumes were '
-                              'created' % len(cls.volume_id_list))
-                if cls.volume_list:
-                    # We could not create all the volumes, though we were able
-                    # to create *some* of the volumes. This is typically
-                    # because the backing file size of the volume group is
-                    # too small.
-                    for volid in cls.volume_id_list:
-                        cls.client.delete_volume(volid)
-                        cls.client.wait_for_resource_deletion(volid)
-                raise
+            volume = cls.create_volume(metadata=cls.metadata)
+            resp, volume = cls.client.get_volume(volume['id'])
+            cls.volume_list.append(volume)
+            cls.volume_id_list.append(volume['id'])
 
     @classmethod
     def tearDownClass(cls):
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 82924a5..a8b0a8d 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -25,6 +25,7 @@
     _interface = 'json'
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(VolumesNegativeTest, cls).setUpClass()
         cls.client = cls.volumes_client
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 84c9501..2ce3a4f 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -24,14 +24,10 @@
     _interface = "json"
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(VolumesSnapshotTest, cls).setUpClass()
-        try:
-            cls.volume_origin = cls.create_volume()
-        except Exception:
-            LOG.exception("setup failed")
-            cls.tearDownClass()
-            raise
+        cls.volume_origin = cls.create_volume()
 
     @classmethod
     def tearDownClass(cls):
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 4d2573b..41445d7 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -56,6 +56,7 @@
                              [str_vol(v) for v in fetched_list]))
 
     @classmethod
+    @test.safe_setup
     def setUpClass(cls):
         super(VolumesV2ListTestJSON, cls).setUpClass()
         cls.client = cls.volumes_client
@@ -65,23 +66,10 @@
         cls.volume_id_list = []
         cls.metadata = {'Type': 'work'}
         for i in range(3):
-            try:
-                volume = cls.create_volume(metadata=cls.metadata)
-                resp, volume = cls.client.get_volume(volume['id'])
-                cls.volume_list.append(volume)
-                cls.volume_id_list.append(volume['id'])
-            except Exception:
-                LOG.exception('Failed to create volume. %d volumes were '
-                              'created' % len(cls.volume_id_list))
-                if cls.volume_list:
-                    # We could not create all the volumes, though we were able
-                    # to create *some* of the volumes. This is typically
-                    # because the backing file size of the volume group is
-                    # too small.
-                    for volid in cls.volume_id_list:
-                        cls.client.delete_volume(volid)
-                        cls.client.wait_for_resource_deletion(volid)
-                raise
+            volume = cls.create_volume(metadata=cls.metadata)
+            resp, volume = cls.client.get_volume(volume['id'])
+            cls.volume_list.append(volume)
+            cls.volume_id_list.append(volume['id'])
 
     @classmethod
     def tearDownClass(cls):
diff --git a/tempest/api_schema/compute/aggregates.py b/tempest/api_schema/compute/aggregates.py
new file mode 100644
index 0000000..49793fe
--- /dev/null
+++ b/tempest/api_schema/compute/aggregates.py
@@ -0,0 +1,43 @@
+# 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.
+
+list_aggregates = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'aggregates': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'availability_zone': {'type': ['string', 'null']},
+                        'created_at': {'type': 'string'},
+                        'deleted': {'type': 'boolean'},
+                        'deleted_at': {'type': ['string', 'null']},
+                        'hosts': {'type': 'array'},
+                        'id': {'type': 'integer'},
+                        'metadata': {'type': 'object'},
+                        'name': {'type': 'string'},
+                        'updated_at': {'type': ['string', 'null']}
+                    },
+                    'required': ['availability_zone', 'created_at', 'deleted',
+                                 'deleted_at', 'hosts', 'id', 'metadata',
+                                 'name', 'updated_at']
+                }
+            }
+        },
+        'required': ['aggregates']
+    }
+}
diff --git a/tempest/api_schema/compute/flavors_access.py b/tempest/api_schema/compute/flavors_access.py
new file mode 100644
index 0000000..152e24c
--- /dev/null
+++ b/tempest/api_schema/compute/flavors_access.py
@@ -0,0 +1,34 @@
+# 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.
+
+list_flavor_access = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'flavor_access': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'flavor_id': {'type': 'string'},
+                        'tenant_id': {'type': 'string'},
+                    },
+                    'required': ['flavor_id', 'tenant_id'],
+                }
+            }
+        },
+        'required': ['flavor_access']
+    }
+}
diff --git a/tempest/api_schema/compute/parameter_types.py b/tempest/api_schema/compute/parameter_types.py
new file mode 100644
index 0000000..67c0c9b
--- /dev/null
+++ b/tempest/api_schema/compute/parameter_types.py
@@ -0,0 +1,28 @@
+# 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.
+
+links = {
+    'type': 'array',
+    'items': {
+        'type': 'object',
+        'properties': {
+            'href': {
+                'type': 'string',
+                'format': 'uri'
+            },
+            'rel': {'type': 'string'}
+        },
+        'required': ['href', 'rel']
+    }
+}
diff --git a/tempest/api_schema/compute/v2/floating_ips.py b/tempest/api_schema/compute/v2/floating_ips.py
index 61582ec..648d0bf 100644
--- a/tempest/api_schema/compute/v2/floating_ips.py
+++ b/tempest/api_schema/compute/v2/floating_ips.py
@@ -44,3 +44,33 @@
         'required': ['floating_ips']
     }
 }
+
+floating_ip = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'floating_ip': {
+                'type': 'object',
+                'properties': {
+                    # NOTE: Now the type of 'id' is integer, but here allows
+                    # 'string' also because we will be able to change it to
+                    # 'uuid' in the future.
+                    'id': {'type': ['integer', 'string']},
+                    'pool': {'type': ['string', 'null']},
+                    'instance_id': {'type': ['integer', 'string', 'null']},
+                    'ip': {
+                        'type': 'string',
+                        'format': 'ip-address'
+                    },
+                    'fixed_ip': {
+                        'type': ['string', 'null'],
+                        'format': 'ip-address'
+                    }
+                },
+                'required': ['id', 'pool', 'instance_id', 'ip', 'fixed_ip']
+            }
+        },
+        'required': ['floating_ip']
+    }
+}
diff --git a/tempest/api_schema/compute/v2/images.py b/tempest/api_schema/compute/v2/images.py
index fb4804d..41593c6 100644
--- a/tempest/api_schema/compute/v2/images.py
+++ b/tempest/api_schema/compute/v2/images.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.api_schema.compute import parameter_types
+
 get_image = {
     'status_code': [200],
     'response_body': {
@@ -23,20 +25,7 @@
                     'id': {'type': 'string'},
                     'status': {'type': 'string'},
                     'updated': {'type': 'string'},
-                    'links': {
-                        'type': 'array',
-                        'items': {
-                            'type': 'object',
-                            'properties': {
-                                'href': {
-                                    'type': 'string',
-                                    'format': 'uri'
-                                },
-                                'rel': {'type': 'string'}
-                            },
-                            'required': ['href', 'rel']
-                        }
-                    },
+                    'links': parameter_types.links,
                     'name': {'type': 'string'},
                     'created': {'type': 'string'},
                     'OS-EXT-IMG-SIZE:size': {'type': 'integer'},
@@ -51,20 +40,7 @@
                             # allows 'string' also because we will be able to
                             # change it to 'uuid' in the future.
                             'id': {'type': ['integer', 'string']},
-                            'links': {
-                                'type': 'array',
-                                'items': {
-                                    'type': 'object',
-                                    'properties': {
-                                        'href': {
-                                            'type': 'string',
-                                            'format': 'uri'
-                                        },
-                                        'rel': {'type': 'string'}
-                                    },
-                                    'required': ['href', 'rel']
-                                }
-                            }
+                            'links': parameter_types.links
                         },
                         'required': ['id', 'links']
                     }
@@ -79,3 +55,38 @@
         'required': ['image']
     }
 }
+
+list_images = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'images': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'id': {'type': 'string'},
+                        'links': {
+                            'type': 'array',
+                            'items': {
+                                'type': 'object',
+                                'properties': {
+                                    'href': {
+                                        'type': 'string',
+                                        'format': 'uri'
+                                    },
+                                    'rel': {'type': 'string'}
+                                },
+                                'required': ['href', 'rel']
+                            }
+                        },
+                        'name': {'type': 'string'}
+                    },
+                    'required': ['id', 'links', 'name']
+                }
+            }
+        },
+        'required': ['images']
+    }
+}
diff --git a/tempest/api_schema/compute/v2/security_groups.py b/tempest/api_schema/compute/v2/security_groups.py
new file mode 100644
index 0000000..68b65b4
--- /dev/null
+++ b/tempest/api_schema/compute/v2/security_groups.py
@@ -0,0 +1,38 @@
+# 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.
+
+list_security_groups = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'security_groups': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'id': {'type': ['integer', 'string']},
+                        'name': {'type': 'string'},
+                        'tenant_id': {'type': 'string'},
+                        'rules': {'type': 'array'},
+                        'description': {'type': 'string'},
+                    },
+                    'required': ['id', 'name', 'tenant_id', 'rules',
+                                 'description'],
+                }
+            }
+        },
+        'required': ['security_groups']
+    }
+}
diff --git a/tempest/api_schema/compute/v2/servers.py b/tempest/api_schema/compute/v2/servers.py
index 7f06ca6..b4e6e53 100644
--- a/tempest/api_schema/compute/v2/servers.py
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.api_schema.compute import parameter_types
+
 create_server = {
     'status_code': [202],
     'response_body': {
@@ -25,20 +27,7 @@
                     # as a server id.
                     'id': {'type': ['integer', 'string']},
                     'security_groups': {'type': 'array'},
-                    'links': {
-                        'type': 'array',
-                        'items': {
-                            'type': 'object',
-                            'properties': {
-                                'href': {
-                                    'type': 'string',
-                                    'format': 'uri'
-                                },
-                                'rel': {'type': 'string'}
-                            },
-                            'required': ['href', 'rel']
-                        }
-                    },
+                    'links': parameter_types.links,
                     'adminPass': {'type': 'string'},
                     'OS-DCF:diskConfig': {'type': 'string'}
                 },
diff --git a/tempest/api_schema/compute/v2/volumes.py b/tempest/api_schema/compute/v2/volumes.py
index 16ed7c2..9cfd7e3 100644
--- a/tempest/api_schema/compute/v2/volumes.py
+++ b/tempest/api_schema/compute/v2/volumes.py
@@ -20,10 +20,7 @@
             'volume': {
                 'type': 'object',
                 'properties': {
-                    # NOTE: Now the type of 'id' is integer, but here allows
-                    # 'string' also because we will be able to change it to
-                    # 'uuid' in the future.
-                    'id': {'type': ['integer', 'string']},
+                    'id': {'type': 'string'},
                     'status': {'type': 'string'},
                     'displayName': {'type': ['string', 'null']},
                     'availabilityZone': {'type': 'string'},
@@ -38,11 +35,17 @@
                         'items': {
                             'type': 'object',
                             'properties': {
-                                'id': {'type': ['integer', 'string']},
+                                'id': {'type': 'string'},
                                 'device': {'type': 'string'},
-                                'volumeId': {'type': ['integer', 'string']},
+                                'volumeId': {'type': 'string'},
                                 'serverId': {'type': ['integer', 'string']}
                             }
+                            # NOTE- If volume is not attached to any server
+                            # then, 'attachments' attributes comes as array
+                            # with empty objects "[{}]" due to that elements
+                            # of 'attachments' cannot defined as 'required'.
+                            # If it would come as empty array "[]" then,
+                            # those elements can be defined as 'required'.
                         }
                     }
                 },
@@ -54,3 +57,54 @@
         'required': ['volume']
     }
 }
+
+list_volumes = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'volumes': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'id': {'type': 'string'},
+                        'status': {'type': 'string'},
+                        'displayName': {'type': ['string', 'null']},
+                        'availabilityZone': {'type': 'string'},
+                        'createdAt': {'type': 'string'},
+                        'displayDescription': {'type': ['string', 'null']},
+                        'volumeType': {'type': 'string'},
+                        'snapshotId': {'type': ['string', 'null']},
+                        'metadata': {'type': 'object'},
+                        'size': {'type': 'integer'},
+                        'attachments': {
+                            'type': 'array',
+                            'items': {
+                                'type': 'object',
+                                'properties': {
+                                    'id': {'type': 'string'},
+                                    'device': {'type': 'string'},
+                                    'volumeId': {'type': 'string'},
+                                    'serverId': {'type': ['integer', 'string']}
+                                }
+                                # NOTE- If volume is not attached to any server
+                                # then, 'attachments' attributes comes as array
+                                # with empty object "[{}]" due to that elements
+                                # of 'attachments' cannot defined as 'required'
+                                # If it would come as empty array "[]" then,
+                                # those elements can be defined as 'required'.
+                            }
+                        }
+                    },
+                    'required': ['id', 'status', 'displayName',
+                                 'availabilityZone', 'createdAt',
+                                 'displayDescription', 'volumeType',
+                                 'snapshotId', 'metadata', 'size',
+                                 'attachments']
+                }
+            }
+        },
+        'required': ['volumes']
+    }
+}
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/compute/v3/servers.py
index e69b25f..390962e 100644
--- a/tempest/api_schema/compute/v3/servers.py
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.api_schema.compute import parameter_types
+
 create_server = {
     'status_code': [202],
     'response_body': {
@@ -25,20 +27,7 @@
                     # as a server id.
                     'id': {'type': ['integer', 'string']},
                     'os-security-groups:security_groups': {'type': 'array'},
-                    'links': {
-                        'type': 'array',
-                        'items': {
-                            'type': 'object',
-                            'properties': {
-                                'href': {
-                                    'type': 'string',
-                                    'format': 'uri'
-                                },
-                                'rel': {'type': 'string'}
-                            },
-                            'required': ['href', 'rel']
-                        }
-                    },
+                    'links': parameter_types.links,
                     'admin_password': {'type': 'string'},
                     'os-access-ips:access_ip_v4': {'type': 'string'},
                     'os-access-ips:access_ip_v6': {'type': 'string'}
diff --git a/tempest/auth.py b/tempest/auth.py
index 0e45161..5fc923f 100644
--- a/tempest/auth.py
+++ b/tempest/auth.py
@@ -164,6 +164,8 @@
 
 class KeystoneAuthProvider(AuthProvider):
 
+    token_expiry_threshold = datetime.timedelta(seconds=60)
+
     def __init__(self, credentials, client_type='tempest', interface=None):
         super(KeystoneAuthProvider, self).__init__(credentials, client_type,
                                                    interface)
@@ -293,7 +295,8 @@
         _, access = auth_data
         expiry = datetime.datetime.strptime(access['token']['expires'],
                                             self.EXPIRY_DATE_FORMAT)
-        return expiry <= datetime.datetime.now()
+        return expiry - self.token_expiry_threshold <= \
+            datetime.datetime.utcnow()
 
 
 class KeystoneV3AuthProvider(KeystoneAuthProvider):
@@ -393,4 +396,5 @@
         _, access = auth_data
         expiry = datetime.datetime.strptime(access['expires_at'],
                                             self.EXPIRY_DATE_FORMAT)
-        return expiry <= datetime.datetime.now()
+        return expiry - self.token_expiry_threshold <= \
+            datetime.datetime.utcnow()
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index 932b151..6aa98c4 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -93,8 +93,7 @@
         """Executes sahara command for the given action."""
         flags += ' --endpoint-type %s' % CONF.data_processing.endpoint_type
         return self.cmd_with_auth(
-            # TODO (slukjanov): replace with sahara when new client released
-            'savanna', action, flags, params, admin, fail_ok)
+            'sahara', action, flags, params, admin, fail_ok)
 
     def cmd_with_auth(self, cmd, action, flags='', params='',
                       admin=True, fail_ok=False):
@@ -115,25 +114,19 @@
         cmd = ' '.join([os.path.join(CONF.cli.cli_dir, cmd),
                         flags, action, params])
         LOG.info("running: '%s'" % cmd)
-        cmd_str = cmd
         cmd = shlex.split(cmd)
         result = ''
         result_err = ''
-        try:
-            stdout = subprocess.PIPE
-            stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
-            proc = subprocess.Popen(
-                cmd, stdout=stdout, stderr=stderr)
-            result, result_err = proc.communicate()
-            if not fail_ok and proc.returncode != 0:
-                raise CommandFailed(proc.returncode,
-                                    cmd,
-                                    result,
-                                    stderr=result_err)
-        finally:
-            LOG.debug('output of %s:\n%s' % (cmd_str, result))
-            if not merge_stderr and result_err:
-                LOG.debug('error output of %s:\n%s' % (cmd_str, result_err))
+        stdout = subprocess.PIPE
+        stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
+        proc = subprocess.Popen(
+            cmd, stdout=stdout, stderr=stderr)
+        result, result_err = proc.communicate()
+        if not fail_ok and proc.returncode != 0:
+            raise CommandFailed(proc.returncode,
+                                cmd,
+                                result,
+                                stderr=result_err)
         return result
 
     def assertTableStruct(self, items, field_names):
diff --git a/tempest/cli/output_parser.py b/tempest/cli/output_parser.py
index 4edcd47..80234a3 100644
--- a/tempest/cli/output_parser.py
+++ b/tempest/cli/output_parser.py
@@ -17,6 +17,7 @@
 
 import re
 
+from tempest import exceptions
 from tempest.openstack.common import log as logging
 
 
@@ -37,7 +38,7 @@
     for table_ in tables_:
         if 'Property' not in table_['headers'] \
            or 'Value' not in table_['headers']:
-            raise Exception('Invalid structure of table with details')
+            raise exceptions.InvalidStructure()
         item = {}
         for value in table_['values']:
             item[value[0]] = value[1]
diff --git a/tempest/common/generator/base_generator.py b/tempest/common/generator/base_generator.py
index 7e7a2d6..95d50e2 100644
--- a/tempest/common/generator/base_generator.py
+++ b/tempest/common/generator/base_generator.py
@@ -62,7 +62,7 @@
             "admin_client": {"type": "boolean"},
             "url": {"type": "string"},
             "default_result_code": {"type": "integer"},
-            "json-schema": jsonschema._utils.load_schema("draft4"),
+            "json-schema": {},
             "resources": {
                 "type": "array",
                 "items": {
@@ -105,6 +105,8 @@
                         self.types_dict[type].append(method)
 
     def validate_schema(self, schema):
+        if "json-schema" in schema:
+            jsonschema.Draft4Validator.check_schema(schema['json-schema'])
         jsonschema.validate(schema, self.schema)
 
     def generate(self, schema):
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 88dbe58..934b861 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -15,7 +15,7 @@
 #    under the License.
 
 import collections
-import hashlib
+import inspect
 import json
 from lxml import etree
 import re
@@ -224,44 +224,80 @@
         versions = map(lambda x: x['id'], body)
         return resp, versions
 
-    def _log_request(self, method, req_url, headers, body):
-        self.LOG.info('Request: ' + method + ' ' + req_url)
-        if headers:
-            print_headers = headers
-            if 'X-Auth-Token' in headers and headers['X-Auth-Token']:
-                token = headers['X-Auth-Token']
-                if len(token) > 64 and TOKEN_CHARS_RE.match(token):
-                    print_headers = headers.copy()
-                    print_headers['X-Auth-Token'] = "<Token omitted>"
-            self.LOG.debug('Request Headers: ' + str(print_headers))
-        if body:
-            str_body = str(body)
-            length = len(str_body)
-            self.LOG.debug('Request Body: ' + str_body[:2048])
-            if length >= 2048:
-                self.LOG.debug("Large body (%d) md5 summary: %s", length,
-                               hashlib.md5(str_body).hexdigest())
+    def _find_caller(self):
+        """Find the caller class and test name.
 
-    def _log_response(self, resp, resp_body):
-        status = resp['status']
-        self.LOG.info("Response Status: " + status)
-        headers = resp.copy()
-        del headers['status']
-        if headers.get('x-compute-request-id'):
-            self.LOG.info("Nova/Cinder request id: %s" %
-                          headers.pop('x-compute-request-id'))
-        elif headers.get('x-openstack-request-id'):
-            self.LOG.info("OpenStack request id %s" %
-                          headers.pop('x-openstack-request-id'))
-        if len(headers):
-            self.LOG.debug('Response Headers: ' + str(headers))
-        if resp_body:
-            str_body = str(resp_body)
-            length = len(str_body)
-            self.LOG.debug('Response Body: ' + str_body[:2048])
-            if length >= 2048:
-                self.LOG.debug("Large body (%d) md5 summary: %s", length,
-                               hashlib.md5(str_body).hexdigest())
+        Because we know that the interesting things that call us are
+        test_* methods, and various kinds of setUp / tearDown, we
+        can look through the call stack to find appropriate methods,
+        and the class we were in when those were called.
+        """
+        caller_name = None
+        names = []
+        frame = inspect.currentframe()
+        is_cleanup = False
+        # Start climbing the ladder until we hit a good method
+        while True:
+            try:
+                frame = frame.f_back
+                name = frame.f_code.co_name
+                names.append(name)
+                if re.search("^(test_|setUp|tearDown)", name):
+                    cname = ""
+                    if 'self' in frame.f_locals:
+                        cname = frame.f_locals['self'].__class__.__name__
+                    if 'cls' in frame.f_locals:
+                        cname = frame.f_locals['cls'].__name__
+                    caller_name = cname + ":" + name
+                    break
+                elif re.search("^_run_cleanup", name):
+                    is_cleanup = True
+                else:
+                    cname = ""
+                    if 'self' in frame.f_locals:
+                        cname = frame.f_locals['self'].__class__.__name__
+                    if 'cls' in frame.f_locals:
+                        cname = frame.f_locals['cls'].__name__
+
+                    # the fact that we are running cleanups is indicated pretty
+                    # deep in the stack, so if we see that we want to just
+                    # start looking for a real class name, and declare victory
+                    # once we do.
+                    if is_cleanup and cname:
+                        if not re.search("^RunTest", cname):
+                            caller_name = cname + ":_run_cleanups"
+                            break
+            except Exception:
+                break
+        # prevents frame leaks
+        del frame
+        if caller_name is None:
+            self.LOG.debug("Sane call name not found in %s" % names)
+        return caller_name
+
+    def _get_request_id(self, resp):
+        for i in ('x-openstack-request-id', 'x-compute-request-id'):
+            if i in resp:
+                return resp[i]
+        return ""
+
+    def _log_request(self, method, req_url, resp, secs=""):
+        # if we have the request id, put it in the right part of the log
+        extra = dict(request_id=self._get_request_id(resp))
+        # NOTE(sdague): while we still have 6 callers to this function
+        # we're going to just provide work around on who is actually
+        # providing timings by gracefully adding no content if they don't.
+        # Once we're down to 1 caller, clean this up.
+        if secs:
+            secs = " %.3fs" % secs
+        self.LOG.info(
+            'Request (%s): %s %s %s%s' % (
+                self._find_caller(),
+                resp['status'],
+                method,
+                req_url,
+                secs),
+            extra=extra)
 
     def _parse_resp(self, body):
         if self._get_type() is "json":
@@ -340,11 +376,13 @@
         # Authenticate the request with the auth provider
         req_url, req_headers, req_body = self.auth_provider.auth_request(
             method, url, headers, body, self.filters)
-        self._log_request(method, req_url, req_headers, req_body)
-        # Do the actual request
+
+        # Do the actual request, and time it
+        start = time.time()
         resp, resp_body = self.http_obj.request(
             req_url, method, headers=req_headers, body=req_body)
-        self._log_response(resp, resp_body)
+        end = time.time()
+        self._log_request(method, req_url, resp, secs=(end - start))
         # Verify HTTP response codes
         self.response_checker(method, url, req_headers, req_body, resp,
                               resp_body)
diff --git a/tempest/exceptions/__init__.py b/tempest/exceptions/__init__.py
index 485f532..d313def 100644
--- a/tempest/exceptions/__init__.py
+++ b/tempest/exceptions/__init__.py
@@ -158,3 +158,7 @@
 
 class UnexpectedResponseCode(base.RestClientException):
     message = "Unexpected response code received"
+
+
+class InvalidStructure(base.TempestException):
+    message = "Invalid structure of table with details"
diff --git a/tempest/services/compute/json/aggregates_client.py b/tempest/services/compute/json/aggregates_client.py
index 700a29b..ccb85c4 100644
--- a/tempest/services/compute/json/aggregates_client.py
+++ b/tempest/services/compute/json/aggregates_client.py
@@ -15,6 +15,7 @@
 
 import json
 
+from tempest.api_schema.compute import aggregates as schema
 from tempest.common import rest_client
 from tempest import config
 from tempest import exceptions
@@ -32,6 +33,7 @@
         """Get aggregate list."""
         resp, body = self.get("os-aggregates")
         body = json.loads(body)
+        self.validate_response(schema.list_aggregates, resp, body)
         return resp, body['aggregates']
 
     def get_aggregate(self, aggregate_id):
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index a8111af..bc64117 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -16,6 +16,7 @@
 import json
 import urllib
 
+from tempest.api_schema.compute import flavors_access as schema_access
 from tempest.common import rest_client
 from tempest import config
 
@@ -125,6 +126,7 @@
         """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)
         return resp, body['flavor_access']
 
     def add_flavor_access(self, flavor_id, tenant_id):
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index 2a7e25a..273ada6 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -47,6 +47,7 @@
         body = json.loads(body)
         if resp.status == 404:
             raise exceptions.NotFound(body)
+        self.validate_response(schema.floating_ip, resp, body)
         return resp, body['floating_ip']
 
     def create_floating_ip(self, pool_name=None):
@@ -56,6 +57,7 @@
         post_body = json.dumps(post_body)
         resp, body = self.post(url, post_body)
         body = json.loads(body)
+        self.validate_response(schema.floating_ip, resp, body)
         return resp, body['floating_ip']
 
     def delete_floating_ip(self, floating_ip_id):
diff --git a/tempest/services/compute/json/images_client.py b/tempest/services/compute/json/images_client.py
index deb9c93..2f128f2 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -58,6 +58,7 @@
 
         resp, body = self.get(url)
         body = json.loads(body)
+        self.validate_response(schema.list_images, resp, body)
         return resp, body['images']
 
     def list_images_with_detail(self, params=None):
diff --git a/tempest/services/compute/json/security_groups_client.py b/tempest/services/compute/json/security_groups_client.py
index 899d4ef..9267be7 100644
--- a/tempest/services/compute/json/security_groups_client.py
+++ b/tempest/services/compute/json/security_groups_client.py
@@ -16,6 +16,7 @@
 import json
 import urllib
 
+from tempest.api_schema.compute.v2 import security_groups as schema
 from tempest.common import rest_client
 from tempest import config
 from tempest import exceptions
@@ -38,6 +39,7 @@
 
         resp, body = self.get(url)
         body = json.loads(body)
+        self.validate_response(schema.list_security_groups, resp, body)
         return resp, body['security_groups']
 
     def get_security_group(self, security_group_id):
@@ -119,6 +121,7 @@
         """List all rules for a security group."""
         resp, body = self.get('os-security-groups')
         body = json.loads(body)
+        self.validate_response(schema.list_security_groups, resp, body)
         for sg in body['security_groups']:
             if sg['id'] == security_group_id:
                 return resp, sg['rules']
diff --git a/tempest/services/compute/json/volumes_extensions_client.py b/tempest/services/compute/json/volumes_extensions_client.py
index 451dbac..17468eb 100644
--- a/tempest/services/compute/json/volumes_extensions_client.py
+++ b/tempest/services/compute/json/volumes_extensions_client.py
@@ -42,6 +42,7 @@
 
         resp, body = self.get(url)
         body = json.loads(body)
+        self.validate_response(schema.list_volumes, resp, body)
         return resp, body['volumes']
 
     def list_volumes_with_detail(self, params=None):
@@ -52,6 +53,7 @@
 
         resp, body = self.get(url)
         body = json.loads(body)
+        self.validate_response(schema.list_volumes, resp, body)
         return resp, body['volumes']
 
     def get_volume(self, volume_id):
diff --git a/tempest/services/compute/v3/json/aggregates_client.py b/tempest/services/compute/v3/json/aggregates_client.py
index fddf5df..7f73622 100644
--- a/tempest/services/compute/v3/json/aggregates_client.py
+++ b/tempest/services/compute/v3/json/aggregates_client.py
@@ -15,6 +15,7 @@
 
 import json
 
+from tempest.api_schema.compute import aggregates as schema
 from tempest.common import rest_client
 from tempest import config
 from tempest import exceptions
@@ -32,6 +33,7 @@
         """Get aggregate list."""
         resp, body = self.get("os-aggregates")
         body = json.loads(body)
+        self.validate_response(schema.list_aggregates, resp, body)
         return resp, body['aggregates']
 
     def get_aggregate(self, aggregate_id):
diff --git a/tempest/services/compute/v3/json/flavors_client.py b/tempest/services/compute/v3/json/flavors_client.py
index 656bd84..655e279 100644
--- a/tempest/services/compute/v3/json/flavors_client.py
+++ b/tempest/services/compute/v3/json/flavors_client.py
@@ -16,6 +16,7 @@
 import json
 import urllib
 
+from tempest.api_schema.compute import flavors_access as schema_access
 from tempest.common import rest_client
 from tempest import config
 
@@ -125,6 +126,7 @@
         """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)
         return resp, body['flavor_access']
 
     def add_flavor_access(self, flavor_id, tenant_id):
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 99b4036..58451fb 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -276,10 +276,9 @@
             # Because XML response is not easily
             # converted to the corresponding JSON one
             headers = self.get_headers(accept_type="json")
-        self._log_request(method, url, headers, body)
         resp, resp_body = self.http_obj.request(url, method,
                                                 headers=headers, body=body)
-        self._log_response(resp, resp_body)
+        self._log_request(method, url, resp)
 
         if resp.status in [401, 403]:
             resp_body = json.loads(resp_body)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 285feb3..35d8aa0 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -509,10 +509,9 @@
             # Because XML response is not easily
             # converted to the corresponding JSON one
             headers = self.get_headers(accept_type="json")
-        self._log_request(method, url, headers, body)
         resp, resp_body = self.http_obj.request(url, method,
                                                 headers=headers, body=body)
-        self._log_response(resp, resp_body)
+        self._log_request(method, url, resp)
 
         if resp.status in [401, 403]:
             resp_body = json.loads(resp_body)
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index d6c5bc1..8f42924 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -505,10 +505,9 @@
             # Because XML response is not easily
             # converted to the corresponding JSON one
             headers = self.get_headers(accept_type="json")
-        self._log_request(method, url, headers, body)
         resp, resp_body = self.http_obj.request(url, method,
                                                 headers=headers, body=body)
-        self._log_response(resp, resp_body)
+        self._log_request(method, url, resp)
 
         if resp.status in [401, 403]:
             resp_body = json.loads(resp_body)
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index a804e8e..27f4655 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -144,25 +144,6 @@
         body = json.loads(body)
         return resp, body
 
-    def create_floating_ip(self, ext_network_id, **kwargs):
-        post_body = {
-            'floatingip': kwargs}
-        post_body['floatingip']['floating_network_id'] = ext_network_id
-        body = json.dumps(post_body)
-        uri = '%s/floatingips' % (self.uri_prefix)
-        resp, body = self.post(uri, body=body)
-        body = json.loads(body)
-        return resp, body
-
-    def update_floating_ip(self, floating_ip_id, **kwargs):
-        post_body = {
-            'floatingip': kwargs}
-        body = json.dumps(post_body)
-        uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
-        resp, body = self.put(uri, body)
-        body = json.loads(body)
-        return resp, body
-
     def associate_health_monitor_with_pool(self, health_monitor_id,
                                            pool_id):
         post_body = {
diff --git a/tempest/services/network/network_client_base.py b/tempest/services/network/network_client_base.py
index 41a7aa4..e21abe1 100644
--- a/tempest/services/network/network_client_base.py
+++ b/tempest/services/network/network_client_base.py
@@ -44,7 +44,6 @@
     'security_groups': 'security_groups',
     'security_group_rules': 'security_group_rules',
     'ikepolicy': 'ikepolicies',
-    'floating_ip': 'floatingips',
     'quotas': 'quotas'
 }
 
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index 2a5083c..68bc424 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -166,33 +166,6 @@
         body = _root_tag_fetcher_and_xml_to_json_parse(body)
         return resp, body
 
-    def create_floating_ip(self, ext_network_id, **kwargs):
-        uri = '%s/floatingips' % (self.uri_prefix)
-        floatingip = common.Element('floatingip')
-        floatingip.append(common.Element("floating_network_id",
-                                         ext_network_id))
-        for element, content in kwargs.iteritems():
-            floatingip.append(common.Element(element, content))
-        resp, body = self.post(uri, str(common.Document(floatingip)))
-        body = _root_tag_fetcher_and_xml_to_json_parse(body)
-        return resp, body
-
-    def update_floating_ip(self, floating_ip_id, **kwargs):
-        uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
-        floatingip = common.Element('floatingip')
-        floatingip.add_attr('xmlns:xsi',
-                            'http://www.w3.org/2001/XMLSchema-instance')
-        for element, content in kwargs.iteritems():
-            if content is None:
-                xml_elem = common.Element(element)
-                xml_elem.add_attr("xsi:nil", "true")
-                floatingip.append(xml_elem)
-            else:
-                floatingip.append(common.Element(element, content))
-        resp, body = self.put(uri, str(common.Document(floatingip)))
-        body = _root_tag_fetcher_and_xml_to_json_parse(body)
-        return resp, body
-
     def list_router_interfaces(self, uuid):
         uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
         resp, body = self.get(uri)
diff --git a/tempest/services/object_storage/account_client.py b/tempest/services/object_storage/account_client.py
index 7c3fa85..6e7910e 100644
--- a/tempest/services/object_storage/account_client.py
+++ b/tempest/services/object_storage/account_client.py
@@ -173,12 +173,11 @@
             method=method, url=url, headers=headers, body=body,
             filters=self.filters
         )
-        self._log_request(method, req_url, headers, body)
         # use original body
         resp, resp_body = self.http_obj.request(req_url, method,
                                                 headers=req_headers,
                                                 body=req_body)
-        self._log_response(resp, resp_body)
+        self._log_request(method, req_url, resp)
 
         if resp.status == 401 or resp.status == 403:
             raise exceptions.Unauthorized()
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 77d29a5..49f7f49 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -160,11 +160,10 @@
             filters=self.filters
         )
         # Use original method
-        self._log_request(method, req_url, headers, body)
         resp, resp_body = self.http_obj.request(req_url, method,
                                                 headers=req_headers,
                                                 body=req_body)
-        self._log_response(resp, resp_body)
+        self._log_request(method, req_url, resp)
         if resp.status == 401 or resp.status == 403:
             raise exceptions.Unauthorized()
 
diff --git a/tempest/test.py b/tempest/test.py
index d2568a6..abf42c0 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -24,10 +24,9 @@
 
 import fixtures
 import testresources
+import testscenarios
 import testtools
 
-from oslo.config import cfg
-
 from tempest import clients
 import tempest.common.generator.valid_generator as valid
 from tempest.common import isolated_creds
@@ -407,6 +406,24 @@
         return json.load(open(fn))
 
     @staticmethod
+    def load_tests(*args):
+        """
+        Wrapper for testscenarios to set the mandatory scenarios variable
+        only in case a real test loader is in place. Will be automatically
+        called in case the variable "load_tests" is set.
+        """
+        if getattr(args[0], 'suiteClass', None) is not None:
+            loader, standard_tests, pattern = args
+        else:
+            standard_tests, module, loader = args
+        for test in testtools.iterate_tests(standard_tests):
+            schema_file = getattr(test, '_schema_file', None)
+            if schema_file is not None:
+                setattr(test, 'scenarios',
+                        NegativeAutoTest.generate_scenario(schema_file))
+        return testscenarios.load_tests_apply_scenarios(*args)
+
+    @staticmethod
     def generate_scenario(description_file):
         """
         Generates the test scenario list for a given description.
@@ -429,17 +446,8 @@
         """
         description = NegativeAutoTest.load_schema(description_file)
         LOG.debug(description)
-
-        # NOTE(mkoderer): since this will be executed on import level the
-        # config doesn't have to be in place (e.g. for the pep8 job).
-        # In this case simply return.
-        try:
-            generator = importutils.import_class(
-                CONF.negative.test_generator)()
-        except cfg.ConfigFilesNotFoundError:
-            LOG.critical(
-                "Tempest config not found. Test scenarios aren't created")
-            return
+        generator = importutils.import_class(
+            CONF.negative.test_generator)()
         generator.validate_schema(description)
         schema = description.get("json-schema", None)
         resources = description.get("resources", [])
diff --git a/tempest/tests/negative/test_negative_generators.py b/tempest/tests/negative/test_negative_generators.py
new file mode 100644
index 0000000..f2ed999
--- /dev/null
+++ b/tempest/tests/negative/test_negative_generators.py
@@ -0,0 +1,81 @@
+# Copyright 2014 Deutsche Telekom AG
+# 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 jsonschema
+import mock
+
+import tempest.common.generator.base_generator as base_generator
+from tempest.tests import base
+
+
+class TestNegativeBasicGenerator(base.TestCase):
+    valid_desc = {
+        "name": "list-flavors-with-detail",
+        "http-method": "GET",
+        "url": "flavors/detail",
+        "json-schema": {
+            "type": "object",
+            "properties": {
+                "minRam": {"type": "integer"},
+                "minDisk": {"type": "integer"}
+            }
+        },
+        "resources": ["flavor", "volume", "image"]
+    }
+
+    minimal_desc = {
+        "name": "list-flavors-with-detail",
+        "http-method": "GET",
+        "url": "flavors/detail",
+    }
+
+    add_prop_desc = {
+        "name": "list-flavors-with-detail",
+        "http-method": "GET",
+        "url": "flavors/detail",
+        "unknown_field": [12]
+    }
+
+    invalid_json_schema_desc = {
+        "name": "list-flavors-with-detail",
+        "http-method": "GET",
+        "url": "flavors/detail",
+        "json-schema": {"type": "NotExistingType"}
+    }
+
+    def setUp(self):
+        super(TestNegativeBasicGenerator, self).setUp()
+        self.generator = base_generator.BasicGeneratorSet()
+
+    def _assert_valid_jsonschema_call(self, jsonschema_mock, desc):
+        self.assertEqual(jsonschema_mock.call_count, 1)
+        jsonschema_mock.assert_called_with(desc, self.generator.schema)
+
+    @mock.patch('jsonschema.validate', wraps=jsonschema.validate)
+    def test_validate_schema_with_valid_input(self, jsonschema_mock):
+        self.generator.validate_schema(self.valid_desc)
+        self._assert_valid_jsonschema_call(jsonschema_mock, self.valid_desc)
+
+    @mock.patch('jsonschema.validate', wraps=jsonschema.validate)
+    def test_validate_schema_with_minimal_input(self, jsonschema_mock):
+        self.generator.validate_schema(self.minimal_desc)
+        self._assert_valid_jsonschema_call(jsonschema_mock, self.minimal_desc)
+
+    def test_validate_schema_with_invalid_input(self):
+        self.assertRaises(jsonschema.ValidationError,
+                          self.generator.validate_schema, self.add_prop_desc)
+        self.assertRaises(jsonschema.SchemaError,
+                          self.generator.validate_schema,
+                          self.invalid_json_schema_desc)
diff --git a/tempest/tests/test_auth.py b/tempest/tests/test_auth.py
index b6e15bd..62c20e3 100644
--- a/tempest/tests/test_auth.py
+++ b/tempest/tests/test_auth.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 import copy
+import datetime
 
 from tempest import auth
 from tempest.common import http
@@ -131,6 +132,11 @@
         self.assertEqual(expected['token'], headers['X-Auth-Token'])
         self.assertEqual(expected['body'], body)
 
+    def _auth_data_with_expiry(self, date_as_string):
+        token, access = self.auth_provider.auth_data
+        access['token']['expires'] = date_as_string
+        return token, access
+
     def test_request(self):
         filters = {
             'service': 'compute',
@@ -292,6 +298,25 @@
         expected = 'http://fake_url/'
         self._test_base_url_helper(expected, self.filters)
 
+    def test_token_not_expired(self):
+        expiry_data = datetime.datetime.utcnow() + datetime.timedelta(days=1)
+        auth_data = self._auth_data_with_expiry(
+            expiry_data.strftime(self.auth_provider.EXPIRY_DATE_FORMAT))
+        self.assertFalse(self.auth_provider.is_expired(auth_data))
+
+    def test_token_expired(self):
+        expiry_data = datetime.datetime.utcnow() - datetime.timedelta(hours=1)
+        auth_data = self._auth_data_with_expiry(
+            expiry_data.strftime(self.auth_provider.EXPIRY_DATE_FORMAT))
+        self.assertTrue(self.auth_provider.is_expired(auth_data))
+
+    def test_token_not_expired_to_be_renewed(self):
+        expiry_data = datetime.datetime.utcnow() + \
+            self.auth_provider.token_expiry_threshold / 2
+        auth_data = self._auth_data_with_expiry(
+            expiry_data.strftime(self.auth_provider.EXPIRY_DATE_FORMAT))
+        self.assertTrue(self.auth_provider.is_expired(auth_data))
+
 
 class TestKeystoneV3AuthProvider(TestKeystoneV2AuthProvider):
     _endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog']
@@ -316,6 +341,11 @@
             return ep['url'].replace('v3', replacement)
         return ep['url']
 
+    def _auth_data_with_expiry(self, date_as_string):
+        token, access = self.auth_provider.auth_data
+        access['expires_at'] = date_as_string
+        return token, access
+
     def test_check_credentials_missing_tenant_name(self):
         cred = copy.copy(self.credentials)
         del cred['domain_name']
diff --git a/tempest/tests/test_rest_client.py b/tempest/tests/test_rest_client.py
index da9ab72..0677aa0 100644
--- a/tempest/tests/test_rest_client.py
+++ b/tempest/tests/test_rest_client.py
@@ -43,7 +43,7 @@
         self.useFixture(mockpatch.PatchObject(self.rest_client, '_get_region',
                                               side_effect=self._get_region()))
         self.useFixture(mockpatch.PatchObject(self.rest_client,
-                                              '_log_response'))
+                                              '_log_request'))
 
 
 class TestRestClientHTTPMethods(BaseRestClientTestClass):