Merge "Improve IPV6 parity in Security Group testcases"
diff --git a/doc/source/cleanup.rst b/doc/source/cleanup.rst
new file mode 100644
index 0000000..acd016c
--- /dev/null
+++ b/doc/source/cleanup.rst
@@ -0,0 +1,5 @@
+--------------------------------
+Post Tempest Run Cleanup Utility
+--------------------------------
+
+.. automodule:: tempest.cmd.cleanup
\ No newline at end of file
diff --git a/doc/source/index.rst b/doc/source/index.rst
index d3118ac..bc4fc46 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -29,6 +29,15 @@
    field_guide/thirdparty
    field_guide/unit_tests
 
+---------------------
+Command Documentation
+---------------------
+
+.. toctree::
+   :maxdepth: 1
+
+   cleanup
+
 ==================
 Indices and tables
 ==================
diff --git a/setup.cfg b/setup.cfg
index 5c62710..2e25ace 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -22,6 +22,7 @@
     verify-tempest-config = tempest.cmd.verify_tempest_config:main
     javelin2 = tempest.cmd.javelin:main
     run-tempest-stress = tempest.cmd.run_stress:main
+    tempest-cleanup = tempest.cmd.cleanup:main
 
 [build_sphinx]
 all_files = 1
diff --git a/tempest/api/orchestration/stacks/test_update.py b/tempest/api/orchestration/stacks/test_update.py
deleted file mode 100644
index 98761ac..0000000
--- a/tempest/api/orchestration/stacks/test_update.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#    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 logging
-
-from tempest.api.orchestration import base
-from tempest.common.utils import data_utils
-from tempest import test
-
-
-LOG = logging.getLogger(__name__)
-
-
-class UpdateStackTestJSON(base.BaseOrchestrationTest):
-    _interface = 'json'
-
-    template = '''
-heat_template_version: 2013-05-23
-resources:
-  random1:
-    type: OS::Heat::RandomString
-'''
-    update_template = '''
-heat_template_version: 2013-05-23
-resources:
-  random1:
-    type: OS::Heat::RandomString
-  random2:
-    type: OS::Heat::RandomString
-'''
-
-    def update_stack(self, stack_identifier, template):
-        stack_name = stack_identifier.split('/')[0]
-        self.client.update_stack(
-            stack_identifier=stack_identifier,
-            name=stack_name,
-            template=template)
-        self.client.wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE')
-
-    @test.attr(type='gate')
-    def test_stack_update_nochange(self):
-        stack_name = data_utils.rand_name('heat')
-        stack_identifier = self.create_stack(stack_name, self.template)
-        self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
-        expected_resources = {'random1': 'OS::Heat::RandomString'}
-        self.assertEqual(expected_resources,
-                         self.list_resources(stack_identifier))
-
-        # Update with no changes, resources should be unchanged
-        self.update_stack(stack_identifier, self.template)
-        self.assertEqual(expected_resources,
-                         self.list_resources(stack_identifier))
-
-    @test.attr(type='gate')
-    def test_stack_update_add_remove(self):
-        stack_name = data_utils.rand_name('heat')
-        stack_identifier = self.create_stack(stack_name, self.template)
-        self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
-        initial_resources = {'random1': 'OS::Heat::RandomString'}
-        self.assertEqual(initial_resources,
-                         self.list_resources(stack_identifier))
-
-        # Add one resource via a stack update
-        self.update_stack(stack_identifier, self.update_template)
-        updated_resources = {'random1': 'OS::Heat::RandomString',
-                             'random2': 'OS::Heat::RandomString'}
-        self.assertEqual(updated_resources,
-                         self.list_resources(stack_identifier))
-
-        # Then remove it by updating with the original template
-        self.update_stack(stack_identifier, self.template)
-        self.assertEqual(initial_resources,
-                         self.list_resources(stack_identifier))
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 769f5e0..db2aab5 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -25,9 +25,8 @@
     _interface = "json"
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumeMultiBackendTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumeMultiBackendTest, cls).resource_setup()
         if not CONF.volume_feature_enabled.multi_backend:
             raise cls.skipException("Cinder multi-backend feature disabled")
 
@@ -76,7 +75,7 @@
             self.volume['id'], 'available')
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         # volumes deletion
         vid_prefix = getattr(cls, 'volume_id_list_with_prefix', [])
         for volume_id in vid_prefix:
@@ -93,7 +92,7 @@
         for volume_type_id in volume_type_id_list:
             cls.client.delete_volume_type(volume_type_id)
 
-        super(VolumeMultiBackendTest, cls).tearDownClass()
+        super(VolumeMultiBackendTest, cls).resource_cleanup()
 
     @test.attr(type='smoke')
     def test_backend_name_reporting(self):
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index abbe1e9..720734b 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -22,9 +22,8 @@
     _interface = "json"
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(SnapshotsActionsTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(SnapshotsActionsTest, cls).resource_setup()
         cls.client = cls.snapshots_client
 
         # Create admin volume client
@@ -46,7 +45,7 @@
                                             'available')
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         # Delete the test snapshot
         cls.client.delete_snapshot(cls.snapshot['id'])
         cls.client.wait_for_resource_deletion(cls.snapshot['id'])
@@ -55,7 +54,7 @@
         cls.volumes_client.delete_volume(cls.volume['id'])
         cls.volumes_client.wait_for_resource_deletion(cls.volume['id'])
 
-        super(SnapshotsActionsTest, cls).tearDownClass()
+        super(SnapshotsActionsTest, cls).resource_cleanup()
 
     def tearDown(self):
         # Set snapshot's status to available after test
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index fa3b667..7e24fa4 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -27,8 +27,8 @@
     force_tenant_isolation = True
 
     @classmethod
-    def setUpClass(cls):
-        super(VolumeQuotasAdminTestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumeQuotasAdminTestJSON, cls).resource_setup()
         cls.admin_volume_client = cls.os_adm.volumes_client
         cls.demo_tenant_id = cls.isolated_creds.get_primary_creds().tenant_id
 
diff --git a/tempest/api/volume/admin/test_volume_quotas_negative.py b/tempest/api/volume/admin/test_volume_quotas_negative.py
index 515024f..60a0adb 100644
--- a/tempest/api/volume/admin/test_volume_quotas_negative.py
+++ b/tempest/api/volume/admin/test_volume_quotas_negative.py
@@ -23,9 +23,8 @@
     force_tenant_isolation = True
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumeQuotasNegativeTestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumeQuotasNegativeTestJSON, cls).resource_setup()
         demo_user = cls.isolated_creds.get_primary_creds()
         cls.demo_tenant_id = demo_user.tenant_id
         cls.shared_quota_set = {'gigabytes': 3, 'volumes': 1, 'snapshots': 1}
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index 4a68e05..7820148 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -25,8 +25,8 @@
     _interface = "json"
 
     @classmethod
-    def setUpClass(cls):
-        super(VolumesServicesTestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesServicesTestJSON, cls).resource_setup()
         cls.client = cls.os_adm.volume_services_client
         _, cls.services = cls.client.list_services()
         cls.host_name = cls.services[0]['host']
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index c682866..2d72dd2 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -22,15 +22,15 @@
     _interface = "json"
 
     @classmethod
-    def setUpClass(cls):
-        super(VolumeTypesExtraSpecsTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumeTypesExtraSpecsTest, cls).resource_setup()
         vol_type_name = data_utils.rand_name('Volume-type-')
         _, cls.volume_type = cls.client.create_volume_type(vol_type_name)
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         cls.client.delete_volume_type(cls.volume_type['id'])
-        super(VolumeTypesExtraSpecsTest, cls).tearDownClass()
+        super(VolumeTypesExtraSpecsTest, cls).resource_cleanup()
 
     @test.attr(type='smoke')
     def test_volume_type_extra_specs_list(self):
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index ff4f113..f3eee00 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -25,8 +25,8 @@
     _interface = 'json'
 
     @classmethod
-    def setUpClass(cls):
-        super(ExtraSpecsNegativeTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(ExtraSpecsNegativeTest, cls).resource_setup()
         vol_type_name = data_utils.rand_name('Volume-type-')
         cls.extra_specs = {"spec1": "val1"}
         _, cls.volume_type = cls.client.create_volume_type(
@@ -34,9 +34,9 @@
             extra_specs=cls.extra_specs)
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         cls.client.delete_volume_type(cls.volume_type['id'])
-        super(ExtraSpecsNegativeTest, cls).tearDownClass()
+        super(ExtraSpecsNegativeTest, cls).resource_cleanup()
 
     @test.attr(type='gate')
     def test_update_no_body(self):
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index d6db1df..f85718b 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -22,9 +22,8 @@
     _interface = "json"
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumesActionsTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesActionsTest, cls).resource_setup()
         cls.client = cls.volumes_client
 
         # Create admin volume client
@@ -38,12 +37,12 @@
         cls.client.wait_for_volume_status(cls.volume['id'], 'available')
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         # Delete the test volume
         cls.client.delete_volume(cls.volume['id'])
         cls.client.wait_for_resource_deletion(cls.volume['id'])
 
-        super(VolumesActionsTest, cls).tearDownClass()
+        super(VolumesActionsTest, cls).resource_cleanup()
 
     def _reset_volume_status(self, volume_id, status):
         # Reset the volume status
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 3699e9c..8b90b07 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -27,9 +27,8 @@
     _interface = "json"
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumesBackupsTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesBackupsTest, cls).resource_setup()
 
         if not CONF.volume_feature_enabled.backup:
             raise cls.skipException("Cinder backup feature disabled")
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 8aad058..7f5361d 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -32,9 +32,9 @@
     _interface = 'json'
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         cls.set_network_resources()
-        super(BaseVolumeTest, cls).setUpClass()
+        super(BaseVolumeTest, cls).resource_setup()
 
         if not CONF.service_available.cinder:
             skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
@@ -83,11 +83,11 @@
             raise exceptions.InvalidConfiguration(message=msg)
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         cls.clear_snapshots()
         cls.clear_volumes()
         cls.clear_isolated_creds()
-        super(BaseVolumeTest, cls).tearDownClass()
+        super(BaseVolumeTest, cls).resource_cleanup()
 
     @classmethod
     def create_volume(cls, size=1, **kwargs):
@@ -152,8 +152,8 @@
 class BaseVolumeAdminTest(BaseVolumeTest):
     """Base test case class for all Volume Admin API tests."""
     @classmethod
-    def setUpClass(cls):
-        super(BaseVolumeAdminTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(BaseVolumeAdminTest, cls).resource_setup()
         cls.adm_user = CONF.identity.admin_username
         cls.adm_pass = CONF.identity.admin_password
         cls.adm_tenant = CONF.identity.admin_tenant_name
@@ -187,9 +187,9 @@
             cls.volume_qos_client = cls.os_adm.volume_qos_v2_client
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         cls.clear_qos_specs()
-        super(BaseVolumeAdminTest, cls).tearDownClass()
+        super(BaseVolumeAdminTest, cls).resource_cleanup()
 
     @classmethod
     def create_test_qos_specs(cls, name=None, consumer=None, **kwargs):
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index c026f71..648bd8b 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -24,8 +24,8 @@
     """
 
     @classmethod
-    def setUpClass(cls):
-        super(AvailabilityZoneV2TestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(AvailabilityZoneV2TestJSON, cls).resource_setup()
         cls.client = cls.availability_zone_client
 
     @test.attr(type='gate')
diff --git a/tempest/api/volume/test_qos.py b/tempest/api/volume/test_qos.py
index 8b6ba49..a719b79 100644
--- a/tempest/api/volume/test_qos.py
+++ b/tempest/api/volume/test_qos.py
@@ -25,9 +25,8 @@
     """
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(QosSpecsV2TestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(QosSpecsV2TestJSON, cls).resource_setup()
         # Create admin qos client
         # Create a test shared qos-specs for tests
         cls.qos_name = utils.rand_name(cls.__name__ + '-QoS')
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index 7040891..777d3de 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -20,9 +20,8 @@
 class SnapshotV2MetadataTestJSON(base.BaseVolumeTest):
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(SnapshotV2MetadataTestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(SnapshotV2MetadataTestJSON, cls).resource_setup()
         cls.client = cls.snapshots_client
         # Create a volume
         cls.volume = cls.create_volume()
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index ac760aa..2ec8667 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -22,9 +22,8 @@
 class VolumesV2MetadataTest(base.BaseVolumeTest):
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumesV2MetadataTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2MetadataTest, cls).resource_setup()
         # Create a volume
         cls.volume = cls.create_volume()
         cls.volume_id = cls.volume['id']
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
index 4a6ba03..90ac9c1 100644
--- a/tempest/api/volume/test_volume_transfers.py
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -26,8 +26,8 @@
 class VolumesV2TransfersTest(base.BaseVolumeTest):
 
     @classmethod
-    def setUpClass(cls):
-        super(VolumesV2TransfersTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2TransfersTest, cls).resource_setup()
 
         # Add another tenant to test volume-transfer
         if CONF.compute.allow_tenant_isolation:
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index c87878d..a9bc70a 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -24,9 +24,8 @@
 class VolumesV2ActionsTest(base.BaseVolumeTest):
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumesV2ActionsTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2ActionsTest, cls).resource_setup()
         cls.client = cls.volumes_client
         cls.image_client = cls.os.image_client
 
@@ -45,12 +44,12 @@
         self.image_client.wait_for_resource_deletion(image_id)
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         # Delete the test instance
         cls.servers_client.delete_server(cls.server['id'])
         cls.servers_client.wait_for_server_termination(cls.server['id'])
 
-        super(VolumesV2ActionsTest, cls).tearDownClass()
+        super(VolumesV2ActionsTest, cls).resource_cleanup()
 
     @test.stresstest(class_setup_per='process')
     @test.attr(type='smoke')
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index c9e80aa..edd497c 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -23,9 +23,8 @@
 class VolumesV2ExtendTest(base.BaseVolumeTest):
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumesV2ExtendTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2ExtendTest, cls).resource_setup()
         cls.client = cls.volumes_client
 
     @test.attr(type='gate')
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index a346a17..033beb4 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -26,8 +26,8 @@
 class VolumesV2GetTest(base.BaseVolumeTest):
 
     @classmethod
-    def setUpClass(cls):
-        super(VolumesV2GetTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2GetTest, cls).resource_setup()
         cls.client = cls.volumes_client
 
         cls.name_field = cls.special_fields['name_field']
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 272a41a..016e9ab 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -55,9 +55,8 @@
                              [str_vol(v) for v in fetched_list]))
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumesV2ListTestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2ListTestJSON, cls).resource_setup()
         cls.client = cls.volumes_client
         cls.name = cls.VOLUME_FIELDS[1]
 
@@ -72,12 +71,12 @@
             cls.volume_id_list.append(volume['id'])
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         # Delete the created volumes
         for volid in cls.volume_id_list:
             cls.client.delete_volume(volid)
             cls.client.wait_for_resource_deletion(volid)
-        super(VolumesV2ListTestJSON, cls).tearDownClass()
+        super(VolumesV2ListTestJSON, cls).resource_cleanup()
 
     def _list_by_param_value_and_assert(self, params, with_detail=False):
         """
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 5f0cffa..2b43c63 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -24,9 +24,8 @@
 class VolumesV2NegativeTest(base.BaseVolumeTest):
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumesV2NegativeTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2NegativeTest, cls).resource_setup()
         cls.client = cls.volumes_client
 
         cls.name_field = cls.special_fields['name_field']
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 8390f03..78df1df 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -23,9 +23,8 @@
 class VolumesV2SnapshotTestJSON(base.BaseVolumeTest):
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumesV2SnapshotTestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2SnapshotTestJSON, cls).resource_setup()
         cls.volume_origin = cls.create_volume()
 
         if not CONF.volume_feature_enabled.snapshot:
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index ddecda8..75a62a8 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -24,8 +24,8 @@
 class VolumesV2SnapshotNegativeTestJSON(base.BaseVolumeTest):
 
     @classmethod
-    def setUpClass(cls):
-        super(VolumesV2SnapshotNegativeTestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2SnapshotNegativeTestJSON, cls).resource_setup()
 
         if not CONF.volume_feature_enabled.snapshot:
             raise cls.skipException("Cinder volume snapshots are disabled")
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 3ae227d..cc56873 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -31,9 +31,8 @@
     """
 
     @classmethod
-    @test.safe_setup
-    def setUpClass(cls):
-        super(VolumesV2ListTestJSON, cls).setUpClass()
+    def resource_setup(cls):
+        super(VolumesV2ListTestJSON, cls).resource_setup()
         cls.client = cls.volumes_client
 
         # Create 3 test volumes
@@ -47,12 +46,12 @@
             cls.volume_id_list.append(volume['id'])
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         # Delete the created volumes
         for volid in cls.volume_id_list:
             cls.client.delete_volume(volid)
             cls.client.wait_for_resource_deletion(volid)
-        super(VolumesV2ListTestJSON, cls).tearDownClass()
+        super(VolumesV2ListTestJSON, cls).resource_cleanup()
 
     @test.attr(type='gate')
     def test_volume_list_details_with_multiple_params(self):
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index c33589a..ca6d7fe 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -94,11 +94,11 @@
 
 class ClientTestBase(tempest.test.BaseTestCase):
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if not CONF.cli.enabled:
             msg = "cli testing disabled"
             raise cls.skipException(msg)
-        super(ClientTestBase, cls).setUpClass()
+        super(ClientTestBase, cls).resource_setup()
 
     def __init__(self, *args, **kwargs):
         self.parser = tempest.cli.output_parser
diff --git a/tempest/cli/simple_read_only/compute/test_nova.py b/tempest/cli/simple_read_only/compute/test_nova.py
index 9bac7a6..6e5e077 100644
--- a/tempest/cli/simple_read_only/compute/test_nova.py
+++ b/tempest/cli/simple_read_only/compute/test_nova.py
@@ -41,11 +41,11 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if not CONF.service_available.nova:
             msg = ("%s skipped as Nova is not available" % cls.__name__)
             raise cls.skipException(msg)
-        super(SimpleReadOnlyNovaClientTest, cls).setUpClass()
+        super(SimpleReadOnlyNovaClientTest, cls).resource_setup()
 
     def test_admin_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
diff --git a/tempest/cli/simple_read_only/compute/test_nova_manage.py b/tempest/cli/simple_read_only/compute/test_nova_manage.py
index a4c7aa1..cff543f 100644
--- a/tempest/cli/simple_read_only/compute/test_nova_manage.py
+++ b/tempest/cli/simple_read_only/compute/test_nova_manage.py
@@ -36,7 +36,7 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if not CONF.service_available.nova:
             msg = ("%s skipped as Nova is not available" % cls.__name__)
             raise cls.skipException(msg)
@@ -44,7 +44,7 @@
             msg = ("%s skipped as *-manage commands not available"
                    % cls.__name__)
             raise cls.skipException(msg)
-        super(SimpleReadOnlyNovaManageTest, cls).setUpClass()
+        super(SimpleReadOnlyNovaManageTest, cls).resource_setup()
 
     def test_admin_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
diff --git a/tempest/cli/simple_read_only/data_processing/test_sahara.py b/tempest/cli/simple_read_only/data_processing/test_sahara.py
index 2c6e0e2..751a4ad 100644
--- a/tempest/cli/simple_read_only/data_processing/test_sahara.py
+++ b/tempest/cli/simple_read_only/data_processing/test_sahara.py
@@ -34,11 +34,11 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if not CONF.service_available.sahara:
             msg = "Skipping all Sahara cli tests because it is not available"
             raise cls.skipException(msg)
-        super(SimpleReadOnlySaharaClientTest, cls).setUpClass()
+        super(SimpleReadOnlySaharaClientTest, cls).resource_setup()
 
     @test.attr(type='negative')
     def test_sahara_fake_action(self):
diff --git a/tempest/cli/simple_read_only/image/test_glance.py b/tempest/cli/simple_read_only/image/test_glance.py
index 2fd8212..a9cbadb 100644
--- a/tempest/cli/simple_read_only/image/test_glance.py
+++ b/tempest/cli/simple_read_only/image/test_glance.py
@@ -34,11 +34,11 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if not CONF.service_available.glance:
             msg = ("%s skipped as Glance is not available" % cls.__name__)
             raise cls.skipException(msg)
-        super(SimpleReadOnlyGlanceClientTest, cls).setUpClass()
+        super(SimpleReadOnlyGlanceClientTest, cls).resource_setup()
 
     def test_glance_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
diff --git a/tempest/cli/simple_read_only/network/test_neutron.py b/tempest/cli/simple_read_only/network/test_neutron.py
index 87f6b67..f9f8906 100644
--- a/tempest/cli/simple_read_only/network/test_neutron.py
+++ b/tempest/cli/simple_read_only/network/test_neutron.py
@@ -35,11 +35,11 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if (not CONF.service_available.neutron):
             msg = "Skipping all Neutron cli tests because it is not available"
             raise cls.skipException(msg)
-        super(SimpleReadOnlyNeutronClientTest, cls).setUpClass()
+        super(SimpleReadOnlyNeutronClientTest, cls).resource_setup()
 
     @test.attr(type='smoke')
     def test_neutron_fake_action(self):
diff --git a/tempest/cli/simple_read_only/object_storage/test_swift.py b/tempest/cli/simple_read_only/object_storage/test_swift.py
index 069a384..a162660 100644
--- a/tempest/cli/simple_read_only/object_storage/test_swift.py
+++ b/tempest/cli/simple_read_only/object_storage/test_swift.py
@@ -31,11 +31,11 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if not CONF.service_available.swift:
             msg = ("%s skipped as Swift is not available" % cls.__name__)
             raise cls.skipException(msg)
-        super(SimpleReadOnlySwiftClientTest, cls).setUpClass()
+        super(SimpleReadOnlySwiftClientTest, cls).resource_setup()
 
     def test_swift_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
diff --git a/tempest/cli/simple_read_only/orchestration/test_heat.py b/tempest/cli/simple_read_only/orchestration/test_heat.py
index 430cdf1..7d7f8c9 100644
--- a/tempest/cli/simple_read_only/orchestration/test_heat.py
+++ b/tempest/cli/simple_read_only/orchestration/test_heat.py
@@ -32,12 +32,12 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if (not CONF.service_available.heat):
             msg = ("Skipping all Heat cli tests because it is "
                    "not available")
             raise cls.skipException(msg)
-        super(SimpleReadOnlyHeatClientTest, cls).setUpClass()
+        super(SimpleReadOnlyHeatClientTest, cls).resource_setup()
         cls.heat_template_path = os.path.join(os.path.dirname(
             os.path.dirname(os.path.realpath(__file__))),
             'heat_templates/heat_minimal.yaml')
diff --git a/tempest/cli/simple_read_only/telemetry/test_ceilometer.py b/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
index 1d2822d..45b793b 100644
--- a/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
+++ b/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
@@ -32,12 +32,12 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if (not CONF.service_available.ceilometer):
             msg = ("Skipping all Ceilometer cli tests because it is "
                    "not available")
             raise cls.skipException(msg)
-        super(SimpleReadOnlyCeilometerClientTest, cls).setUpClass()
+        super(SimpleReadOnlyCeilometerClientTest, cls).resource_setup()
 
     def test_ceilometer_meter_list(self):
         self.ceilometer('meter-list')
diff --git a/tempest/cli/simple_read_only/volume/test_cinder.py b/tempest/cli/simple_read_only/volume/test_cinder.py
index e44a577..45f6c41 100644
--- a/tempest/cli/simple_read_only/volume/test_cinder.py
+++ b/tempest/cli/simple_read_only/volume/test_cinder.py
@@ -35,11 +35,11 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if not CONF.service_available.cinder:
             msg = ("%s skipped as Cinder is not available" % cls.__name__)
             raise cls.skipException(msg)
-        super(SimpleReadOnlyCinderClientTest, cls).setUpClass()
+        super(SimpleReadOnlyCinderClientTest, cls).resource_setup()
 
     def test_cinder_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
new file mode 100644
index 0000000..9ae3dfb
--- /dev/null
+++ b/tempest/cmd/cleanup.py
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 Dell Inc.
+# 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.
+# @author: David Paterson
+
+"""
+Utility for cleaning up environment after Tempest run
+
+Runtime Arguments
+-----------------
+
+--init-saved-state: Before you can execute cleanup you must initialize
+the saved state by running it with the --init-saved-state flag
+(creating ./saved_state.json), which protects your deployment from
+cleanup deleting objects you want to keep.  Typically you would run
+cleanup with --init-saved-state prior to a tempest run. If this is not
+the case saved_state.json must be edited, removing objects you want
+cleanup to delete.
+
+--dry-run: Creates a report (dry_run.json) of the tenants that will be
+cleaned up (in the "_tenants_to_clean" array), and the global objects
+that will be removed (tenants, users, flavors and images).  Once
+cleanup is executed in normal mode, running it again with --dry-run
+should yield an empty report.
+
+**NOTE**: The _tenants_to_clean array in dry-run.json lists the
+tenants that cleanup will loop through and delete child objects, not
+delete the tenant itself. This may differ from the tenants array as you
+can clean the tempest and alternate tempest tenants but not delete the
+tenants themselves.  This is actually the default behavior.
+
+**Normal mode**: running with no arguments, will query your deployment and
+build a list of objects to delete after filtering out out the objects
+found in saved_state.json and based on the
+--preserve-tempest-conf-objects and
+--delete-tempest-conf-objects flags.
+
+By default the tempest and alternate tempest users and tenants are not
+deleted and the admin user specified in tempest.conf is never deleted.
+
+Please run with --help to see full list of options.
+"""
+import argparse
+import json
+import sys
+
+from tempest import auth
+from tempest import clients
+from tempest.cmd import cleanup_service
+from tempest import config
+from tempest.openstack.common import log as logging
+
+SAVED_STATE_JSON = "saved_state.json"
+DRY_RUN_JSON = "dry_run.json"
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class Cleanup(object):
+
+    def __init__(self):
+        self.admin_mgr = clients.AdminManager()
+        self.dry_run_data = {}
+        self.json_data = {}
+        self._init_options()
+
+        self.admin_id = ""
+        self.admin_role_id = ""
+        self.admin_tenant_id = ""
+        self._init_admin_ids()
+
+        self.admin_role_added = []
+
+        # available services
+        self.tenant_services = cleanup_service.get_tenant_cleanup_services()
+        self.global_services = cleanup_service.get_global_cleanup_services()
+        cleanup_service.init_conf()
+
+    def run(self):
+        opts = self.options
+        if opts.init_saved_state:
+            self._init_state()
+            return
+
+        self._load_json()
+        self._cleanup()
+
+    def _cleanup(self):
+        LOG.debug("Begin cleanup")
+        is_dry_run = self.options.dry_run
+        is_preserve = self.options.preserve_tempest_conf_objects
+        is_save_state = False
+
+        if is_dry_run:
+            self.dry_run_data["_tenants_to_clean"] = {}
+            f = open(DRY_RUN_JSON, 'w+')
+
+        admin_mgr = self.admin_mgr
+        # Always cleanup tempest and alt tempest tenants unless
+        # they are in saved state json. Therefore is_preserve is False
+        kwargs = {'data': self.dry_run_data,
+                  'is_dry_run': is_dry_run,
+                  'saved_state_json': self.json_data,
+                  'is_preserve': False,
+                  'is_save_state': is_save_state}
+        tenant_service = cleanup_service.TenantService(admin_mgr, **kwargs)
+        tenants = tenant_service.list()
+        LOG.debug("Process %s tenants" % len(tenants))
+
+        # Loop through list of tenants and clean them up.
+        for tenant in tenants:
+            self._add_admin(tenant['id'])
+            self._clean_tenant(tenant)
+
+        kwargs = {'data': self.dry_run_data,
+                  'is_dry_run': is_dry_run,
+                  'saved_state_json': self.json_data,
+                  'is_preserve': is_preserve,
+                  'is_save_state': is_save_state}
+        for service in self.global_services:
+            svc = service(admin_mgr, **kwargs)
+            svc.run()
+
+        if is_dry_run:
+            f.write(json.dumps(self.dry_run_data, sort_keys=True,
+                               indent=2, separators=(',', ': ')))
+            f.close()
+
+        self._remove_admin_user_roles()
+
+    def _remove_admin_user_roles(self):
+        tenant_ids = self.admin_role_added
+        LOG.debug("Removing admin user roles where needed for tenants: %s"
+                  % tenant_ids)
+        for tenant_id in tenant_ids:
+            self._remove_admin_role(tenant_id)
+
+    def _clean_tenant(self, tenant):
+        LOG.debug("Cleaning tenant:  %s " % tenant['name'])
+        is_dry_run = self.options.dry_run
+        dry_run_data = self.dry_run_data
+        is_preserve = self.options.preserve_tempest_conf_objects
+        tenant_id = tenant['id']
+        tenant_name = tenant['name']
+        tenant_data = None
+        if is_dry_run:
+            tenant_data = dry_run_data["_tenants_to_clean"][tenant_id] = {}
+            tenant_data['name'] = tenant_name
+
+        kwargs = {"username": CONF.identity.admin_username,
+                  "password": CONF.identity.admin_password,
+                  "tenant_name": tenant['name']}
+        mgr = clients.Manager(credentials=auth.get_credentials(**kwargs))
+        kwargs = {'data': tenant_data,
+                  'is_dry_run': is_dry_run,
+                  'saved_state_json': None,
+                  'is_preserve': is_preserve,
+                  'is_save_state': False,
+                  'tenant_id': tenant_id}
+        for service in self.tenant_services:
+            svc = service(mgr, **kwargs)
+            svc.run()
+
+    def _init_admin_ids(self):
+        id_cl = self.admin_mgr.identity_client
+
+        tenant = id_cl.get_tenant_by_name(CONF.identity.admin_tenant_name)
+        self.admin_tenant_id = tenant['id']
+
+        user = id_cl.get_user_by_username(self.admin_tenant_id,
+                                          CONF.identity.admin_username)
+        self.admin_id = user['id']
+
+        _, roles = id_cl.list_roles()
+        for role in roles:
+            if role['name'] == CONF.identity.admin_role:
+                self.admin_role_id = role['id']
+                break
+
+    def _init_options(self):
+        parser = argparse.ArgumentParser(
+            description='Cleanup after tempest run')
+        parser.add_argument('--init-saved-state', action="store_true",
+                            dest='init_saved_state', default=False,
+                            help="Creates JSON file: " + SAVED_STATE_JSON +
+                            ", representing the current state of your "
+                            "deployment,  specifically objects types "
+                            "Tempest creates and destroys during a run. "
+                            "You must run with this flag prior to "
+                            "executing cleanup.")
+        parser.add_argument('--preserve-tempest-conf-objects',
+                            action="store_true",
+                            dest='preserve_tempest_conf_objects',
+                            default=True, help="Do not delete the "
+                            "tempest and alternate tempest users and "
+                            "tenants, so they may be used for future "
+                            "tempest runs. By default this is argument "
+                            "is true.")
+        parser.add_argument('--delete-tempest-conf-objects',
+                            action="store_false",
+                            dest='preserve_tempest_conf_objects',
+                            default=False,
+                            help="Delete the tempest and "
+                            "alternate tempest users and tenants.")
+        parser.add_argument('--dry-run', action="store_true",
+                            dest='dry_run', default=False,
+                            help="Generate JSON file:" + DRY_RUN_JSON +
+                            ", that reports the objects that would have "
+                            "been deleted had a full cleanup been run.")
+
+        self.options = parser.parse_args()
+
+    def _add_admin(self, tenant_id):
+        id_cl = self.admin_mgr.identity_client
+        needs_role = True
+        _, roles = id_cl.list_user_roles(tenant_id, self.admin_id)
+        for role in roles:
+            if role['id'] == self.admin_role_id:
+                needs_role = False
+                LOG.debug("User already had admin privilege for this tenant")
+        if needs_role:
+            LOG.debug("Adding admin priviledge for : %s" % tenant_id)
+            id_cl.assign_user_role(tenant_id, self.admin_id,
+                                   self.admin_role_id)
+            self.admin_role_added.append(tenant_id)
+
+    def _remove_admin_role(self, tenant_id):
+        LOG.debug("Remove admin user role for tenant: %s" % tenant_id)
+        # Must initialize AdminManager for each user role
+        # Otherwise authentication exception is thrown, weird
+        id_cl = clients.AdminManager().identity_client
+        if (self._tenant_exists(tenant_id)):
+            try:
+                id_cl.remove_user_role(tenant_id, self.admin_id,
+                                       self.admin_role_id)
+            except Exception as ex:
+                LOG.exception("Failed removing role from tenant which still"
+                              "exists, exception: %s" % ex)
+
+    def _tenant_exists(self, tenant_id):
+        id_cl = self.admin_mgr.identity_client
+        try:
+            t = id_cl.get_tenant(tenant_id)
+            LOG.debug("Tenant is: %s" % str(t))
+            return True
+        except Exception as ex:
+            LOG.debug("Tenant no longer exists? %s" % ex)
+            return False
+
+    def _init_state(self):
+        LOG.debug("Initializing saved state.")
+        data = {}
+        admin_mgr = self.admin_mgr
+        kwargs = {'data': data,
+                  'is_dry_run': False,
+                  'saved_state_json': data,
+                  'is_preserve': False,
+                  'is_save_state': True}
+        for service in self.global_services:
+            svc = service(admin_mgr, **kwargs)
+            svc.run()
+
+        f = open(SAVED_STATE_JSON, 'w+')
+        f.write(json.dumps(data,
+                           sort_keys=True, indent=2, separators=(',', ': ')))
+        f.close()
+
+    def _load_json(self):
+        try:
+            json_file = open(SAVED_STATE_JSON)
+            self.json_data = json.load(json_file)
+            json_file.close()
+        except IOError as ex:
+            LOG.exception("Failed loading saved state, please be sure you"
+                          " have first run cleanup with --init-saved-state "
+                          "flag prior to running tempest. Exception: %s" % ex)
+            sys.exit(ex)
+        except Exception as ex:
+            LOG.exception("Exception parsing saved state json : %s" % ex)
+            sys.exit(ex)
+
+
+def main():
+    cleanup = Cleanup()
+    cleanup.run()
+    LOG.info('Cleanup finished!')
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
new file mode 100644
index 0000000..f5f0db3
--- /dev/null
+++ b/tempest/cmd/cleanup_service.py
@@ -0,0 +1,1066 @@
+#!/usr/bin/env python
+
+# Copyright 2014 Dell Inc.
+#
+#    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.
+'''
+Created on Sep 3, 2014
+
+@author: David_Paterson
+'''
+from tempest import config
+from tempest.openstack.common import log as logging
+from tempest import test
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+CONF_USERS = None
+CONF_TENANTS = None
+CONF_PUB_NETWORK = None
+CONF_PRIV_NETWORK_NAME = None
+CONF_PUB_ROUTER = None
+CONF_FLAVORS = None
+CONF_IMAGES = None
+
+IS_CEILOMETER = None
+IS_CINDER = None
+IS_GLANCE = None
+IS_HEAT = None
+IS_NEUTRON = None
+IS_NOVA = None
+
+
+def init_conf():
+    global CONF_USERS
+    global CONF_TENANTS
+    global CONF_PUB_NETWORK
+    global CONF_PRIV_NETWORK_NAME
+    global CONF_PUB_ROUTER
+    global CONF_FLAVORS
+    global CONF_IMAGES
+
+    global IS_CEILOMETER
+    global IS_CINDER
+    global IS_GLANCE
+    global IS_HEAT
+    global IS_NEUTRON
+    global IS_NOVA
+
+    CONF_USERS = [CONF.identity.admin_username, CONF.identity.username,
+                  CONF.identity.alt_username]
+    CONF_TENANTS = [CONF.identity.admin_tenant_name,
+                    CONF.identity.tenant_name,
+                    CONF.identity.alt_tenant_name]
+    CONF_PUB_NETWORK = CONF.network.public_network_id
+    CONF_PRIV_NETWORK_NAME = CONF.compute.fixed_network_name
+    CONF_PUB_ROUTER = CONF.network.public_router_id
+    CONF_FLAVORS = [CONF.compute.flavor_ref, CONF.compute.flavor_ref_alt]
+    CONF_IMAGES = [CONF.compute.image_ref, CONF.compute.image_ref_alt]
+
+    IS_CEILOMETER = CONF.service_available.ceilometer
+    IS_CINDER = CONF.service_available.cinder
+    IS_GLANCE = CONF.service_available.glance
+    IS_HEAT = CONF.service_available.heat
+    IS_NEUTRON = CONF.service_available.neutron
+    IS_NOVA = CONF.service_available.nova
+
+
+class BaseService(object):
+    def __init__(self, kwargs):
+        self.client = None
+        for key, value in kwargs.items():
+            setattr(self, key, value)
+
+    def _filter_by_tenant_id(self, item_list):
+        if (item_list is None
+                or len(item_list) == 0
+                or not hasattr(self, 'tenant_id')
+                or self.tenant_id is None
+                or 'tenant_id' not in item_list[0]):
+            return item_list
+
+        _filtered_list = []
+        for item in item_list:
+            if item['tenant_id'] == self.tenant_id:
+                _filtered_list.append(item)
+        return _filtered_list
+
+    def list(self):
+        pass
+
+    def delete(self):
+        pass
+
+    def dry_run(self):
+        pass
+
+    def save_state(self):
+        pass
+
+    def run(self):
+        if self.is_dry_run:
+            self.dry_run()
+        elif self.is_save_state:
+            self.save_state()
+        else:
+            self.delete()
+
+
+class SnapshotService(BaseService):
+
+    def __init__(self, manager, **kwargs):
+        super(SnapshotService, self).__init__(kwargs)
+        self.client = manager.snapshots_client
+
+    def list(self):
+        client = self.client
+        __, snaps = client.list_snapshots()
+        LOG.debug("List count, %s Snapshots" % len(snaps))
+        return snaps
+
+    def delete(self):
+        snaps = self.list()
+        client = self.client
+        for snap in snaps:
+            try:
+                client.delete_snapshot(snap['id'])
+            except Exception as e:
+                LOG.exception("Delete Snapshot exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        snaps = self.list()
+        self.data['snapshots'] = snaps
+
+
+class ServerService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(ServerService, self).__init__(kwargs)
+        self.client = manager.servers_client
+
+    def list(self):
+        client = self.client
+        _, servers_body = client.list_servers()
+        servers = servers_body['servers']
+        LOG.debug("List count, %s Servers" % len(servers))
+        return servers
+
+    def delete(self):
+        client = self.client
+        servers = self.list()
+        for server in servers:
+            try:
+                client.delete_server(server['id'])
+            except Exception as e:
+                LOG.exception("Delete Server exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        servers = self.list()
+        self.data['servers'] = servers
+
+
+class ServerGroupService(ServerService):
+
+    def list(self):
+        client = self.client
+        _, sgs = client.list_server_groups()
+        LOG.debug("List count, %s Server Groups" % len(sgs))
+        return sgs
+
+    def delete(self):
+        client = self.client
+        sgs = self.list()
+        for sg in sgs:
+            try:
+                client.delete_server_group(sg['id'])
+            except Exception as e:
+                LOG.exception("Delete Server Group exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        sgs = self.list()
+        self.data['server_groups'] = sgs
+
+
+class StackService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(StackService, self).__init__(kwargs)
+        self.client = manager.orchestration_client
+
+    def list(self):
+        client = self.client
+        _, stacks = client.list_stacks()
+        LOG.debug("List count, %s Stacks" % len(stacks))
+        return stacks
+
+    def delete(self):
+        client = self.client
+        stacks = self.list()
+        for stack in stacks:
+            try:
+                client.delete_stack(stack['id'])
+            except Exception as e:
+                LOG.exception("Delete Stack exception: %s " % e)
+                pass
+
+    def dry_run(self):
+        stacks = self.list()
+        self.data['stacks'] = stacks
+
+
+class KeyPairService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(KeyPairService, self).__init__(kwargs)
+        self.client = manager.keypairs_client
+
+    def list(self):
+        client = self.client
+        _, keypairs = client.list_keypairs()
+        LOG.debug("List count, %s Keypairs" % len(keypairs))
+        return keypairs
+
+    def delete(self):
+        client = self.client
+        keypairs = self.list()
+        for k in keypairs:
+            try:
+                name = k['keypair']['name']
+                client.delete_keypair(name)
+            except Exception as e:
+                LOG.exception("Delete Keypairs exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        keypairs = self.list()
+        self.data['keypairs'] = keypairs
+
+
+class SecurityGroupService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(SecurityGroupService, self).__init__(kwargs)
+        self.client = manager.security_groups_client
+
+    def list(self):
+        client = self.client
+        _, secgrps = client.list_security_groups()
+        secgrp_del = [grp for grp in secgrps if grp['name'] != 'default']
+        LOG.debug("List count, %s Security Groups" % len(secgrp_del))
+        return secgrp_del
+
+    def delete(self):
+        client = self.client
+        secgrp_del = self.list()
+        for g in secgrp_del:
+            try:
+                client.delete_security_group(g['id'])
+            except Exception as e:
+                LOG.exception("Delete Security Groups exception: %s" % e)
+
+    def dry_run(self):
+        secgrp_del = self.list()
+        self.data['security_groups'] = secgrp_del
+
+
+class FloatingIpService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(FloatingIpService, self).__init__(kwargs)
+        self.client = manager.floating_ips_client
+
+    def list(self):
+        client = self.client
+        _, floating_ips = client.list_floating_ips()
+        LOG.debug("List count, %s Floating IPs" % len(floating_ips))
+        return floating_ips
+
+    def delete(self):
+        client = self.client
+        floating_ips = self.list()
+        for f in floating_ips:
+            try:
+                client.delete_floating_ip(f['id'])
+            except Exception as e:
+                LOG.exception("Delete Floating IPs exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        floating_ips = self.list()
+        self.data['floating_ips'] = floating_ips
+
+
+class VolumeService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(VolumeService, self).__init__(kwargs)
+        self.client = manager.volumes_client
+
+    def list(self):
+        client = self.client
+        _, vols = client.list_volumes()
+        LOG.debug("List count, %s Volumes" % len(vols))
+        return vols
+
+    def delete(self):
+        client = self.client
+        vols = self.list()
+        for v in vols:
+            try:
+                client.delete_volume(v['id'])
+            except Exception as e:
+                LOG.exception("Delete Volume exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        vols = self.list()
+        self.data['volumes'] = vols
+
+
+# Begin network service classes
+class NetworkService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(NetworkService, self).__init__(kwargs)
+        self.client = manager.network_client
+
+    def list(self):
+        client = self.client
+        _, networks = client.list_networks()
+        networks = self._filter_by_tenant_id(networks['networks'])
+        # filter out networks declared in tempest.conf
+        if self.is_preserve:
+            networks = [network for network in networks
+                        if (network['name'] != CONF_PRIV_NETWORK_NAME
+                            and network['id'] != CONF_PUB_NETWORK)]
+        LOG.debug("List count, %s Networks" % networks)
+        return networks
+
+    def delete(self):
+        client = self.client
+        networks = self.list()
+        for n in networks:
+            try:
+                client.delete_network(n['id'])
+            except Exception as e:
+                LOG.exception("Delete Network exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        networks = self.list()
+        self.data['networks'] = networks
+
+
+class NetworkIpSecPolicyService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, ipsecpols = client.list_ipsecpolicies()
+        ipsecpols = ipsecpols['ipsecpolicies']
+        ipsecpols = self._filter_by_tenant_id(ipsecpols)
+        LOG.debug("List count, %s IP Security Policies" % len(ipsecpols))
+        return ipsecpols
+
+    def delete(self):
+        client = self.client
+        ipsecpols = self.list()
+        for ipsecpol in ipsecpols:
+            try:
+                client.delete_ipsecpolicy(ipsecpol['id'])
+            except Exception as e:
+                LOG.exception("Delete IP Securty Policy exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        ipsecpols = self.list()
+        self.data['ip_security_policies'] = ipsecpols
+
+
+class NetworkFwPolicyService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, fwpols = client.list_firewall_policies()
+        fwpols = fwpols['firewall_policies']
+        fwpols = self._filter_by_tenant_id(fwpols)
+        LOG.debug("List count, %s Firewall Policies" % len(fwpols))
+        return fwpols
+
+    def delete(self):
+        client = self.client
+        fwpols = self.list()
+        for fwpol in fwpols:
+            try:
+                client.delete_firewall_policy(fwpol['id'])
+            except Exception as e:
+                LOG.exception("Delete Firewall Policy exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        fwpols = self.list()
+        self.data['firewall_policies'] = fwpols
+
+
+class NetworkFwRulesService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, fwrules = client.list_firewall_rules()
+        fwrules = fwrules['firewall_rules']
+        fwrules = self._filter_by_tenant_id(fwrules)
+        LOG.debug("List count, %s Firewall Rules" % len(fwrules))
+        return fwrules
+
+    def delete(self):
+        client = self.client
+        fwrules = self.list()
+        for fwrule in fwrules:
+            try:
+                client.delete_firewall_rule(fwrule['id'])
+            except Exception as e:
+                LOG.exception("Delete Firewall Rule exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        fwrules = self.list()
+        self.data['firewall_rules'] = fwrules
+
+
+class NetworkIkePolicyService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, ikepols = client.list_ikepolicies()
+        ikepols = ikepols['ikepolicies']
+        ikepols = self._filter_by_tenant_id(ikepols)
+        LOG.debug("List count, %s IKE Policies" % len(ikepols))
+        return ikepols
+
+    def delete(self):
+        client = self.client
+        ikepols = self.list()
+        for ikepol in ikepols:
+            try:
+                client.delete_firewall_rule(ikepol['id'])
+            except Exception as e:
+                LOG.exception("Delete IKE Policy exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        ikepols = self.list()
+        self.data['ike_policies'] = ikepols
+
+
+class NetworkVpnServiceService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, vpnsrvs = client.list_vpnservices()
+        vpnsrvs = vpnsrvs['vpnservices']
+        vpnsrvs = self._filter_by_tenant_id(vpnsrvs)
+        LOG.debug("List count, %s VPN Services" % len(vpnsrvs))
+        return vpnsrvs
+
+    def delete(self):
+        client = self.client
+        vpnsrvs = self.list()
+        for vpnsrv in vpnsrvs:
+            try:
+                client.delete_vpnservice(vpnsrv['id'])
+            except Exception as e:
+                LOG.exception("Delete VPN Service exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        vpnsrvs = self.list()
+        self.data['vpn_services'] = vpnsrvs
+
+
+class NetworkFloatingIpService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, flips = client.list_floatingips()
+        flips = flips['floatingips']
+        flips = self._filter_by_tenant_id(flips)
+        LOG.debug("List count, %s Network Floating IPs" % len(flips))
+        return flips
+
+    def delete(self):
+        client = self.client
+        flips = self.list()
+        for flip in flips:
+            try:
+                client.delete_floatingip(flip['id'])
+            except Exception as e:
+                LOG.exception("Delete Network Floating IP exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        flips = self.list()
+        self.data['floating_ips'] = flips
+
+
+class NetworkRouterService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, routers = client.list_routers()
+        routers = routers['routers']
+        routers = self._filter_by_tenant_id(routers)
+        if self.is_preserve:
+            routers = [router for router in routers
+                       if router['id'] != CONF_PUB_ROUTER]
+
+        LOG.debug("List count, %s Routers" % len(routers))
+        return routers
+
+    def delete(self):
+        client = self.client
+        routers = self.list()
+        for router in routers:
+            try:
+                rid = router['id']
+                _, ports = client.list_router_interfaces(rid)
+                ports = ports['ports']
+                for port in ports:
+                    subid = port['fixed_ips'][0]['subnet_id']
+                    client.remove_router_interface_with_subnet_id(rid, subid)
+                    client.delete_router(rid)
+            except Exception as e:
+                LOG.exception("Delete Router exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        routers = self.list()
+        self.data['routers'] = routers
+
+
+class NetworkHealthMonitorService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, hms = client.list_health_monitors()
+        hms = hms['health_monitors']
+        hms = self._filter_by_tenant_id(hms)
+        LOG.debug("List count, %s Health Monitors" % len(hms))
+        return hms
+
+    def delete(self):
+        client = self.client
+        hms = self.list()
+        for hm in hms:
+            try:
+                client.delete_health_monitor(hm['id'])
+            except Exception as e:
+                LOG.exception("Delete Health Monitor exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        hms = self.list()
+        self.data['health_monitors'] = hms
+
+
+class NetworkMemberService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, members = client.list_members()
+        members = members['members']
+        members = self._filter_by_tenant_id(members)
+        LOG.debug("List count, %s Members" % len(members))
+        return members
+
+    def delete(self):
+        client = self.client
+        members = self.list()
+        for member in members:
+            try:
+                client.delete_member(member['id'])
+            except Exception as e:
+                LOG.exception("Delete Member exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        members = self.list()
+        self.data['members'] = members
+
+
+class NetworkVipService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, vips = client.list_vips()
+        vips = vips['vips']
+        vips = self._filter_by_tenant_id(vips)
+        LOG.debug("List count, %s VIPs" % len(vips))
+        return vips
+
+    def delete(self):
+        client = self.client
+        vips = self.list()
+        for vip in vips:
+            try:
+                client.delete_vip(vip['id'])
+            except Exception as e:
+                LOG.exception("Delete VIP exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        vips = self.list()
+        self.data['vips'] = vips
+
+
+class NetworkPoolService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, pools = client.list_pools()
+        pools = pools['pools']
+        pools = self._filter_by_tenant_id(pools)
+        LOG.debug("List count, %s Pools" % len(pools))
+        return pools
+
+    def delete(self):
+        client = self.client
+        pools = self.list()
+        for pool in pools:
+            try:
+                client.delete_pool(pool['id'])
+            except Exception as e:
+                LOG.exception("Delete Pool exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        pools = self.list()
+        self.data['pools'] = pools
+
+
+class NetworMeteringLabelRuleService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, rules = client.list_metering_label_rules()
+        rules = rules['metering_label_rules']
+        rules = self._filter_by_tenant_id(rules)
+        LOG.debug("List count, %s Metering Label Rules" % len(rules))
+        return rules
+
+    def delete(self):
+        client = self.client
+        rules = self.list()
+        for rule in rules:
+            try:
+                client.delete_metering_label_rule(rule['id'])
+            except Exception as e:
+                LOG.exception("Delete Metering Label Rule exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        rules = self.list()
+        self.data['rules'] = rules
+
+
+class NetworMeteringLabelService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, labels = client.list_metering_labels()
+        labels = labels['metering_labels']
+        labels = self._filter_by_tenant_id(labels)
+        LOG.debug("List count, %s Metering Labels" % len(labels))
+        return labels
+
+    def delete(self):
+        client = self.client
+        labels = self.list()
+        for label in labels:
+            try:
+                client.delete_metering_label(label['id'])
+            except Exception as e:
+                LOG.exception("Delete Metering Label exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        labels = self.list()
+        self.data['labels'] = labels
+
+
+class NetworkPortService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, ports = client.list_ports()
+        ports = ports['ports']
+        ports = self._filter_by_tenant_id(ports)
+        LOG.debug("List count, %s Ports" % len(ports))
+        return ports
+
+    def delete(self):
+        client = self.client
+        ports = self.list()
+        for port in ports:
+            try:
+                client.delete_port(port['id'])
+            except Exception as e:
+                LOG.exception("Delete Port exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        ports = self.list()
+        self.data['ports'] = ports
+
+
+class NetworkSubnetService(NetworkService):
+
+    def list(self):
+        client = self.client
+        _, subnets = client.list_subnets()
+        subnets = subnets['subnets']
+        subnets = self._filter_by_tenant_id(subnets)
+        LOG.debug("List count, %s Subnets" % len(subnets))
+        return subnets
+
+    def delete(self):
+        client = self.client
+        subnets = self.list()
+        for subnet in subnets:
+            try:
+                client.delete_subnet(subnet['id'])
+            except Exception as e:
+                LOG.exception("Delete Subnet exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        subnets = self.list()
+        self.data['subnets'] = subnets
+
+
+# Telemetry services
+class TelemetryAlarmService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(TelemetryAlarmService, self).__init__(kwargs)
+        self.client = manager.telemetry_client
+
+    def list(self):
+        client = self.client
+        _, alarms = client.list_alarms()
+        LOG.debug("List count, %s Alarms" % len(alarms))
+        return alarms
+
+    def delete(self):
+        client = self.client
+        alarms = self.list()
+        for alarm in alarms:
+            try:
+                client.delete_alarm(alarm['id'])
+            except Exception as e:
+                LOG.exception("Delete Alarms exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        alarms = self.list()
+        self.data['alarms'] = alarms
+
+
+# begin global services
+class FlavorService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(FlavorService, self).__init__(kwargs)
+        self.client = manager.flavors_client
+
+    def list(self):
+        client = self.client
+        _, flavors = client.list_flavors({"is_public": None})
+        if not self.is_save_state:
+            # recreate list removing saved flavors
+            flavors = [flavor for flavor in flavors if flavor['id']
+                       not in self.saved_state_json['flavors'].keys()]
+
+        if self.is_preserve:
+            flavors = [flavor for flavor in flavors
+                       if flavor['id'] not in CONF_FLAVORS]
+        LOG.debug("List count, %s Flavors after reconcile" % len(flavors))
+        return flavors
+
+    def delete(self):
+        client = self.client
+        flavors = self.list()
+        for flavor in flavors:
+            try:
+                client.delete_flavor(flavor['id'])
+            except Exception as e:
+                LOG.exception("Delete Flavor exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        flavors = self.list()
+        self.data['flavors'] = flavors
+
+    def save_state(self):
+        flavors = self.list()
+        flavor_data = self.data['flavors'] = {}
+        for flavor in flavors:
+            flavor_data[flavor['id']] = flavor['name']
+
+
+class ImageService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(ImageService, self).__init__(kwargs)
+        self.client = manager.images_client
+
+    def list(self):
+        client = self.client
+        _, images = client.list_images({"all_tenants": True})
+        if not self.is_save_state:
+            images = [image for image in images if image['id']
+                      not in self.saved_state_json['images'].keys()]
+        if self.is_preserve:
+            images = [image for image in images
+                      if image['id'] not in CONF_IMAGES]
+        LOG.debug("List count, %s Images after reconcile" % len(images))
+        return images
+
+    def delete(self):
+        client = self.client
+        images = self.list()
+        for image in images:
+            try:
+                client.delete_image(image['id'])
+            except Exception as e:
+                LOG.exception("Delete Image exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        images = self.list()
+        self.data['images'] = images
+
+    def save_state(self):
+        images = self.list()
+        image_data = self.data['images'] = {}
+        for image in images:
+            image_data[image['id']] = image['name']
+
+
+class IdentityService(BaseService):
+    def __init__(self, manager, **kwargs):
+        super(IdentityService, self).__init__(kwargs)
+        self.client = manager.identity_client
+
+
+class UserService(IdentityService):
+
+    def list(self):
+        client = self.client
+        _, users = client.get_users()
+
+        if not self.is_save_state:
+            users = [user for user in users if user['id']
+                     not in self.saved_state_json['users'].keys()]
+
+        if self.is_preserve:
+            users = [user for user in users if user['name']
+                     not in CONF_USERS]
+
+        elif not self.is_save_state:  # Never delete admin user
+            users = [user for user in users if user['name'] !=
+                     CONF.identity.admin_username]
+
+        LOG.debug("List count, %s Users after reconcile" % len(users))
+        return users
+
+    def delete(self):
+        client = self.client
+        users = self.list()
+        for user in users:
+            try:
+                client.delete_user(user['id'])
+            except Exception as e:
+                LOG.exception("Delete User exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        users = self.list()
+        self.data['users'] = users
+
+    def save_state(self):
+        users = self.list()
+        user_data = self.data['users'] = {}
+        for user in users:
+            user_data[user['id']] = user['name']
+
+
+class RoleService(IdentityService):
+
+    def list(self):
+        client = self.client
+        try:
+            _, roles = client.list_roles()
+            # reconcile roles with saved state and never list admin role
+            if not self.is_save_state:
+                roles = [role for role in roles if
+                         (role['id'] not in
+                          self.saved_state_json['roles'].keys()
+                          and role['name'] != CONF.identity.admin_role)]
+                LOG.debug("List count, %s Roles after reconcile" % len(roles))
+            return roles
+        except Exception as ex:
+            LOG.exception("Cannot retrieve Roles, exception: %s" % ex)
+            return []
+
+    def delete(self):
+        client = self.client
+        roles = self.list()
+        for role in roles:
+            try:
+                client.delete_role(role['id'])
+            except Exception as e:
+                LOG.exception("Delete Role exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        roles = self.list()
+        self.data['roles'] = roles
+
+    def save_state(self):
+        roles = self.list()
+        role_data = self.data['roles'] = {}
+        for role in roles:
+            role_data[role['id']] = role['name']
+
+
+class TenantService(IdentityService):
+
+    def list(self):
+        client = self.client
+        _, tenants = client.list_tenants()
+        if not self.is_save_state:
+            tenants = [tenant for tenant in tenants if (tenant['id']
+                       not in self.saved_state_json['tenants'].keys()
+                       and tenant['name'] != CONF.identity.admin_tenant_name)]
+
+        if self.is_preserve:
+            tenants = [tenant for tenant in tenants if tenant['name']
+                       not in CONF_TENANTS]
+
+        LOG.debug("List count, %s Tenants after reconcile" % len(tenants))
+        return tenants
+
+    def delete(self):
+        client = self.client
+        tenants = self.list()
+        for tenant in tenants:
+            try:
+                client.delete_tenant(tenant['id'])
+            except Exception as e:
+                LOG.exception("Delete Tenant exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        tenants = self.list()
+        self.data['tenants'] = tenants
+
+    def save_state(self):
+        tenants = self.list()
+        tenant_data = self.data['tenants'] = {}
+        for tenant in tenants:
+            tenant_data[tenant['id']] = tenant['name']
+
+
+class DomainService(BaseService):
+
+    def __init__(self, manager, **kwargs):
+        super(DomainService, self).__init__(kwargs)
+        self.client = manager.identity_v3_client
+
+    def list(self):
+        client = self.client
+        _, domains = client.list_domains()
+        if not self.is_save_state:
+            domains = [domain for domain in domains if domain['id']
+                       not in self.saved_state_json['domains'].keys()]
+
+        LOG.debug("List count, %s Domains after reconcile" % len(domains))
+        return domains
+
+    def delete(self):
+        client = self.client
+        domains = self.list()
+        for domain in domains:
+            try:
+                client.update_domain(domain['id'], enabled=False)
+                client.delete_domain(domain['id'])
+            except Exception as e:
+                LOG.exception("Delete Domain exception: %s" % e)
+                pass
+
+    def dry_run(self):
+        domains = self.list()
+        self.data['domains'] = domains
+
+    def save_state(self):
+        domains = self.list()
+        domain_data = self.data['domains'] = {}
+        for domain in domains:
+            domain_data[domain['id']] = domain['name']
+
+
+def get_tenant_cleanup_services():
+    tenant_services = []
+
+    if IS_CEILOMETER:
+        tenant_services.append(TelemetryAlarmService)
+    if IS_NOVA:
+        tenant_services.append(ServerService)
+        tenant_services.append(KeyPairService)
+        tenant_services.append(SecurityGroupService)
+        tenant_services.append(ServerGroupService)
+        if not IS_NEUTRON:
+            tenant_services.append(FloatingIpService)
+    if IS_HEAT:
+        tenant_services.append(StackService)
+    if IS_NEUTRON:
+        if test.is_extension_enabled('vpnaas', 'network'):
+            tenant_services.append(NetworkIpSecPolicyService)
+            tenant_services.append(NetworkIkePolicyService)
+            tenant_services.append(NetworkVpnServiceService)
+        if test.is_extension_enabled('fwaas', 'network'):
+            tenant_services.append(NetworkFwPolicyService)
+            tenant_services.append(NetworkFwRulesService)
+        if test.is_extension_enabled('lbaas', 'network'):
+            tenant_services.append(NetworkHealthMonitorService)
+            tenant_services.append(NetworkMemberService)
+            tenant_services.append(NetworkVipService)
+            tenant_services.append(NetworkPoolService)
+        if test.is_extension_enabled('metering', 'network'):
+            tenant_services.append(NetworMeteringLabelRuleService)
+            tenant_services.append(NetworMeteringLabelService)
+        tenant_services.append(NetworkRouterService)
+        tenant_services.append(NetworkFloatingIpService)
+        tenant_services.append(NetworkPortService)
+        tenant_services.append(NetworkSubnetService)
+        tenant_services.append(NetworkService)
+    if IS_CINDER:
+        tenant_services.append(SnapshotService)
+        tenant_services.append(VolumeService)
+    return tenant_services
+
+
+def get_global_cleanup_services():
+    global_services = []
+    if IS_NOVA:
+        global_services.append(FlavorService)
+    if IS_GLANCE:
+        global_services.append(ImageService)
+    global_services.append(UserService)
+    global_services.append(TenantService)
+    global_services.append(DomainService)
+    global_services.append(RoleService)
+    return global_services
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index b3b4fbf..79207cd 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -49,8 +49,8 @@
     """Base class for scenario tests. Uses tempest own clients. """
 
     @classmethod
-    def setUpClass(cls):
-        super(ScenarioTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(ScenarioTest, cls).resource_setup()
         # Using tempest client for isolated credentials as well
         cls.isolated_creds = isolated_creds.IsolatedCreds(
             cls.__name__, network_resources=cls.network_resources)
@@ -474,8 +474,8 @@
             raise cls.skipException('Neutron not available')
 
     @classmethod
-    def setUpClass(cls):
-        super(NetworkScenarioTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(NetworkScenarioTest, cls).resource_setup()
         cls.tenant_id = cls.manager.identity_client.tenant_id
         cls.check_preconditions()
 
@@ -1002,8 +1002,8 @@
 
 class BaremetalScenarioTest(ScenarioTest):
     @classmethod
-    def setUpClass(cls):
-        super(BaremetalScenarioTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(BaremetalScenarioTest, cls).resource_setup()
 
         if (not CONF.service_available.ironic or
            not CONF.baremetal.driver_enabled):
@@ -1134,8 +1134,8 @@
     """
 
     @classmethod
-    def setUpClass(cls):
-        super(EncryptionScenarioTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(EncryptionScenarioTest, cls).resource_setup()
         cls.admin_volume_types_client = cls.admin_manager.volume_types_client
 
     def _wait_for_volume_status(self, status):
@@ -1181,8 +1181,8 @@
     """
 
     @classmethod
-    def setUpClass(cls):
-        super(OrchestrationScenarioTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(OrchestrationScenarioTest, cls).resource_setup()
         if not CONF.service_available.heat:
             raise cls.skipException("Heat support is required")
 
@@ -1226,9 +1226,9 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         cls.set_network_resources()
-        super(SwiftScenarioTest, cls).setUpClass()
+        super(SwiftScenarioTest, cls).resource_setup()
         if not CONF.service_available.swift:
             skip_msg = ("%s skipped as swift is not available" %
                         cls.__name__)
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 3ad5c69..75769ce 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -33,8 +33,8 @@
     Deletes aggregate
     """
     @classmethod
-    def setUpClass(cls):
-        super(TestAggregatesBasicOps, cls).setUpClass()
+    def resource_setup(cls):
+        super(TestAggregatesBasicOps, cls).resource_setup()
         cls.aggregates_client = cls.manager.aggregates_client
         cls.hosts_client = cls.manager.hosts_client
 
diff --git a/tempest/scenario/test_dashboard_basic_ops.py b/tempest/scenario/test_dashboard_basic_ops.py
index 4fcc70a..f218fb2 100644
--- a/tempest/scenario/test_dashboard_basic_ops.py
+++ b/tempest/scenario/test_dashboard_basic_ops.py
@@ -34,9 +34,9 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         cls.set_network_resources()
-        super(TestDashboardBasicOps, cls).setUpClass()
+        super(TestDashboardBasicOps, cls).resource_setup()
 
         if not CONF.service_available.horizon:
             raise cls.skipException("Horizon support is required")
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
index 71b8a7f..b111939 100644
--- a/tempest/scenario/test_large_ops.py
+++ b/tempest/scenario/test_large_ops.py
@@ -38,12 +38,12 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         if CONF.scenario.large_ops_number < 1:
             raise cls.skipException("large_ops_number not set to multiple "
                                     "instances")
         cls.set_network_resources()
-        super(TestLargeOpsScenario, cls).setUpClass()
+        super(TestLargeOpsScenario, cls).resource_setup()
 
     def _wait_for_server_status(self, status):
         for server in self.servers:
diff --git a/tempest/scenario/test_load_balancer_basic.py b/tempest/scenario/test_load_balancer_basic.py
index 21680c2..9e404c8 100644
--- a/tempest/scenario/test_load_balancer_basic.py
+++ b/tempest/scenario/test_load_balancer_basic.py
@@ -57,8 +57,8 @@
             raise cls.skipException(msg)
 
     @classmethod
-    def setUpClass(cls):
-        super(TestLoadBalancerBasic, cls).setUpClass()
+    def resource_setup(cls):
+        super(TestLoadBalancerBasic, cls).resource_setup()
         cls.check_preconditions()
         cls.servers_keypairs = {}
         cls.members = []
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 0277593..58a028f 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -50,10 +50,10 @@
             raise cls.skipException(msg)
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         # Create no network resources for these tests.
         cls.set_network_resources()
-        super(TestNetworkAdvancedServerOps, cls).setUpClass()
+        super(TestNetworkAdvancedServerOps, cls).resource_setup()
 
     def _setup_network_and_servers(self):
         self.keypair = self.create_keypair()
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 904f248..de60745 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -88,10 +88,10 @@
             raise cls.skipException(msg)
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         # Create no network resources for these tests.
         cls.set_network_resources()
-        super(TestNetworkBasicOps, cls).setUpClass()
+        super(TestNetworkBasicOps, cls).resource_setup()
         for ext in ['router', 'security-group']:
             if not test.is_extension_enabled(ext, 'network'):
                 msg = "%s extension not enabled." % ext
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 658d336..188dea8 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -138,10 +138,10 @@
             raise cls.skipException(msg)
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         # Create no network resources for these tests.
         cls.set_network_resources()
-        super(TestSecurityGroupsBasicOps, cls).setUpClass()
+        super(TestSecurityGroupsBasicOps, cls).resource_setup()
         # TODO(mnewby) Consider looking up entities as needed instead
         # of storing them as collections on the class.
         cls.floating_ips = {}
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 463f5aa..c53e22b 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -35,9 +35,9 @@
     """
 
     @classmethod
-    def setUpClass(cls):
+    def resource_setup(cls):
         cls.set_network_resources()
-        super(TestServerAdvancedOps, cls).setUpClass()
+        super(TestServerAdvancedOps, cls).resource_setup()
 
         if CONF.compute.flavor_ref_alt == CONF.compute.flavor_ref:
             msg = "Skipping test - flavor_ref and flavor_ref_alt are identical"
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index b38b1a3..eb636f7 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -35,8 +35,7 @@
      * Create a security group to control network access in instance
      * Add simple permissive rules to the security group
      * Launch an instance
-     * Pause/unpause the instance
-     * Suspend/resume the instance
+     * Perform ssh to instance
      * Terminate the instance
     """
 
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index f2c3dcd..8ea2814 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -51,8 +51,8 @@
     """
 
     @classmethod
-    def setUpClass(cls):
-        super(TestStampPattern, cls).setUpClass()
+    def resource_setup(cls):
+        super(TestStampPattern, cls).resource_setup()
 
         if not CONF.volume_feature_enabled.snapshot:
             raise cls.skipException("Cinder volume snapshots are disabled")
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index fdda423..a20db5c 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -36,8 +36,8 @@
      * Check written content in the instance booted from snapshot
     """
     @classmethod
-    def setUpClass(cls):
-        super(TestVolumeBootPattern, cls).setUpClass()
+    def resource_setup(cls):
+        super(TestVolumeBootPattern, cls).resource_setup()
 
         if not CONF.volume_feature_enabled.snapshot:
             raise cls.skipException("Cinder volume snapshots are disabled")
diff --git a/tempest/services/compute/json/images_client.py b/tempest/services/compute/json/images_client.py
index 9877391..4af8331 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -76,7 +76,7 @@
     def get_image(self, image_id):
         """Returns the details of a single image."""
         resp, body = self.get("images/%s" % str(image_id))
-        self.expected_success(200, resp)
+        self.expected_success(200, resp.status)
         body = json.loads(body)
         self.validate_response(schema.get_image, resp, body)
         return resp, body['image']
diff --git a/tempest/services/compute/xml/images_client.py b/tempest/services/compute/xml/images_client.py
index 6b15404..94acf36 100644
--- a/tempest/services/compute/xml/images_client.py
+++ b/tempest/services/compute/xml/images_client.py
@@ -127,7 +127,7 @@
     def get_image(self, image_id):
         """Returns the details of a single image."""
         resp, body = self.get("images/%s" % str(image_id))
-        self.expected_success(200, resp)
+        self.expected_success(200, resp.status)
         body = self._parse_image(etree.fromstring(body))
         return resp, body
 
diff --git a/tempest/thirdparty/boto/test.py b/tempest/thirdparty/boto/test.py
index f94d880..3496dce 100644
--- a/tempest/thirdparty/boto/test.py
+++ b/tempest/thirdparty/boto/test.py
@@ -195,8 +195,8 @@
     """Recommended to use as base class for boto related test."""
 
     @classmethod
-    def setUpClass(cls):
-        super(BotoTestCase, cls).setUpClass()
+    def resource_setup(cls):
+        super(BotoTestCase, cls).resource_setup()
         cls.conclusion = decision_maker()
         cls.os = cls.get_client_manager()
         # The trash contains cleanup functions and paramaters in tuples
@@ -245,7 +245,7 @@
             raise self.failureException, "BotoServerError not raised"
 
     @classmethod
-    def tearDownClass(cls):
+    def resource_cleanup(cls):
         """Calls the callables added by addResourceCleanUp,
         when you overwrite this function don't forget to call this too.
         """
@@ -264,7 +264,7 @@
             finally:
                 del cls._resource_trash_bin[key]
         cls.clear_isolated_creds()
-        super(BotoTestCase, cls).tearDownClass()
+        super(BotoTestCase, cls).resource_cleanup()
         # NOTE(afazekas): let the super called even on exceptions
         # The real exceptions already logged, if the super throws another,
         # does not causes hidden issues
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index c0d3f7a..f3f11fd 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -30,8 +30,8 @@
 class InstanceRunTest(boto_test.BotoTestCase):
 
     @classmethod
-    def setUpClass(cls):
-        super(InstanceRunTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(InstanceRunTest, cls).resource_setup()
         if not cls.conclusion['A_I_IMAGES_READY']:
             raise cls.skipException("".join(("EC2 ", cls.__name__,
                                     ": requires ami/aki/ari manifest")))
diff --git a/tempest/thirdparty/boto/test_ec2_keys.py b/tempest/thirdparty/boto/test_ec2_keys.py
index 698e3e1..c3e1e2a 100644
--- a/tempest/thirdparty/boto/test_ec2_keys.py
+++ b/tempest/thirdparty/boto/test_ec2_keys.py
@@ -26,8 +26,8 @@
 class EC2KeysTest(boto_test.BotoTestCase):
 
     @classmethod
-    def setUpClass(cls):
-        super(EC2KeysTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(EC2KeysTest, cls).resource_setup()
         cls.client = cls.os.ec2api_client
         cls.ec = cls.ec2_error_code
 
diff --git a/tempest/thirdparty/boto/test_ec2_network.py b/tempest/thirdparty/boto/test_ec2_network.py
index 792dde3..a75fb7b 100644
--- a/tempest/thirdparty/boto/test_ec2_network.py
+++ b/tempest/thirdparty/boto/test_ec2_network.py
@@ -20,8 +20,8 @@
 class EC2NetworkTest(boto_test.BotoTestCase):
 
     @classmethod
-    def setUpClass(cls):
-        super(EC2NetworkTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(EC2NetworkTest, cls).resource_setup()
         cls.client = cls.os.ec2api_client
 
     # Note(afazekas): these tests for things duable without an instance
diff --git a/tempest/thirdparty/boto/test_ec2_security_groups.py b/tempest/thirdparty/boto/test_ec2_security_groups.py
index 7d9bdab..fb3d32b 100644
--- a/tempest/thirdparty/boto/test_ec2_security_groups.py
+++ b/tempest/thirdparty/boto/test_ec2_security_groups.py
@@ -20,8 +20,8 @@
 class EC2SecurityGroupTest(boto_test.BotoTestCase):
 
     @classmethod
-    def setUpClass(cls):
-        super(EC2SecurityGroupTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(EC2SecurityGroupTest, cls).resource_setup()
         cls.client = cls.os.ec2api_client
 
     def test_create_authorize_security_group(self):
diff --git a/tempest/thirdparty/boto/test_ec2_volumes.py b/tempest/thirdparty/boto/test_ec2_volumes.py
index b50c6b0..9cee8a4 100644
--- a/tempest/thirdparty/boto/test_ec2_volumes.py
+++ b/tempest/thirdparty/boto/test_ec2_volumes.py
@@ -29,8 +29,8 @@
 class EC2VolumesTest(boto_test.BotoTestCase):
 
     @classmethod
-    def setUpClass(cls):
-        super(EC2VolumesTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(EC2VolumesTest, cls).resource_setup()
 
         if not CONF.service_available.cinder:
             skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
diff --git a/tempest/thirdparty/boto/test_s3_buckets.py b/tempest/thirdparty/boto/test_s3_buckets.py
index 1576492..342fc0e 100644
--- a/tempest/thirdparty/boto/test_s3_buckets.py
+++ b/tempest/thirdparty/boto/test_s3_buckets.py
@@ -20,8 +20,8 @@
 class S3BucketsTest(boto_test.BotoTestCase):
 
     @classmethod
-    def setUpClass(cls):
-        super(S3BucketsTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(S3BucketsTest, cls).resource_setup()
         cls.client = cls.os.s3_client
 
     def test_create_and_get_delete_bucket(self):
diff --git a/tempest/thirdparty/boto/test_s3_ec2_images.py b/tempest/thirdparty/boto/test_s3_ec2_images.py
index 389e25c..f5dec95 100644
--- a/tempest/thirdparty/boto/test_s3_ec2_images.py
+++ b/tempest/thirdparty/boto/test_s3_ec2_images.py
@@ -26,8 +26,8 @@
 class S3ImagesTest(boto_test.BotoTestCase):
 
     @classmethod
-    def setUpClass(cls):
-        super(S3ImagesTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(S3ImagesTest, cls).resource_setup()
         if not cls.conclusion['A_I_IMAGES_READY']:
             raise cls.skipException("".join(("EC2 ", cls.__name__,
                                     ": requires ami/aki/ari manifest")))
diff --git a/tempest/thirdparty/boto/test_s3_objects.py b/tempest/thirdparty/boto/test_s3_objects.py
index db3c1cf..43774c2 100644
--- a/tempest/thirdparty/boto/test_s3_objects.py
+++ b/tempest/thirdparty/boto/test_s3_objects.py
@@ -24,8 +24,8 @@
 class S3BucketsTest(boto_test.BotoTestCase):
 
     @classmethod
-    def setUpClass(cls):
-        super(S3BucketsTest, cls).setUpClass()
+    def resource_setup(cls):
+        super(S3BucketsTest, cls).resource_setup()
         cls.client = cls.os.s3_client
 
     def test_create_get_delete_object(self):