Merge "Uncap jsonschema"
diff --git a/.gitreview b/.gitreview
index 84b5114..a475594 100644
--- a/.gitreview
+++ b/.gitreview
@@ -1,4 +1,4 @@
 [gerrit]
-host=review.openstack.org
+host=review.opendev.org
 port=29418
 project=openstack/tempest.git
diff --git a/.zuul.yaml b/.zuul.yaml
index 7d77b71..e3210fe 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -8,10 +8,10 @@
       test setup. To run a multi-node test inherit from devstack-tempest and
       set the nodeset to a multi-node one.
     required-projects:
-      - git.openstack.org/openstack/tempest
+      - opendev.org/openstack/tempest
     timeout: 7200
     roles:
-      - zuul: git.openstack.org/openstack-dev/devstack
+      - zuul: opendev.org/openstack/devstack
     vars:
       devstack_services:
         tempest: true
@@ -55,10 +55,10 @@
     description: |
       Base Tempest IPv6 job.
     required-projects:
-      - git.openstack.org/openstack/tempest
+      - opendev.org/openstack/tempest
     timeout: 7200
     roles:
-      - zuul: git.openstack.org/openstack-dev/devstack
+      - zuul: opendev.org/openstack/devstack
     vars:
       devstack_services:
         tempest: true
@@ -113,24 +113,24 @@
       periodic-tempest-dsvm-oslo-latest-full-master.
     timeout: 10800
     required-projects:
-      - git.openstack.org/openstack/oslo.cache
-      - git.openstack.org/openstack/oslo.concurrency
-      - git.openstack.org/openstack/oslo.config
-      - git.openstack.org/openstack/oslo.context
-      - git.openstack.org/openstack/oslo.db
-      - git.openstack.org/openstack/oslo.i18n
-      - git.openstack.org/openstack/oslo.log
-      - git.openstack.org/openstack/oslo.messaging
-      - git.openstack.org/openstack/oslo.middleware
-      - git.openstack.org/openstack/oslo.policy
-      - git.openstack.org/openstack/oslo.privsep
-      - git.openstack.org/openstack/oslo.reports
-      - git.openstack.org/openstack/oslo.rootwrap
-      - git.openstack.org/openstack/oslo.serialization
-      - git.openstack.org/openstack/oslo.service
-      - git.openstack.org/openstack/oslo.utils
-      - git.openstack.org/openstack/oslo.versionedobjects
-      - git.openstack.org/openstack/oslo.vmware
+      - opendev.org/openstack/oslo.cache
+      - opendev.org/openstack/oslo.concurrency
+      - opendev.org/openstack/oslo.config
+      - opendev.org/openstack/oslo.context
+      - opendev.org/openstack/oslo.db
+      - opendev.org/openstack/oslo.i18n
+      - opendev.org/openstack/oslo.log
+      - opendev.org/openstack/oslo.messaging
+      - opendev.org/openstack/oslo.middleware
+      - opendev.org/openstack/oslo.policy
+      - opendev.org/openstack/oslo.privsep
+      - opendev.org/openstack/oslo.reports
+      - opendev.org/openstack/oslo.rootwrap
+      - opendev.org/openstack/oslo.serialization
+      - opendev.org/openstack/oslo.service
+      - opendev.org/openstack/oslo.utils
+      - opendev.org/openstack/oslo.versionedobjects
+      - opendev.org/openstack/oslo.vmware
 
 - job:
     name: tempest-full-parallel
@@ -354,82 +354,82 @@
       - ^tempest/hacking/.*$
       - ^tempest/tests/.*$
     required-projects:
-      - git.openstack.org/openstack/airship-tempest-plugin
-      - git.openstack.org/openstack/almanach
-      - git.openstack.org/openstack/aodh
-      - git.openstack.org/openstack/barbican-tempest-plugin
-      - git.openstack.org/openstack/blazar-tempest-plugin
-      - git.openstack.org/openstack/ceilometer
-      - git.openstack.org/openstack/cinder-tempest-plugin
-      - git.openstack.org/openstack/cloudkitty-tempest-plugin
-      - git.openstack.org/openstack/congress-tempest-plugin
-      - git.openstack.org/openstack/designate-tempest-plugin
-      - git.openstack.org/openstack/ec2api-tempest-plugin
-      - git.openstack.org/openstack/freezer
-      - git.openstack.org/openstack/freezer-api
-      - git.openstack.org/openstack/freezer-tempest-plugin
-      - git.openstack.org/openstack/gabbi-tempest
-      - git.openstack.org/openstack/gce-api
-      - git.openstack.org/openstack/glare
-      - git.openstack.org/openstack/heat-tempest-plugin
-      - git.openstack.org/openstack/intel-nfv-ci-tests
-      - git.openstack.org/openstack/ironic-tempest-plugin
-      - git.openstack.org/openstack/ironic-inspector
-      - git.openstack.org/openstack/keystone-tempest-plugin
-      - git.openstack.org/openstack/kingbird
-      - git.openstack.org/openstack/kuryr-tempest-plugin
-      - git.openstack.org/openstack/magnum
-      - git.openstack.org/openstack/magnum-tempest-plugin
-      - git.openstack.org/openstack/manila
-      - git.openstack.org/openstack/manila-tempest-plugin
-      - git.openstack.org/openstack/mistral-tempest-plugin
-      - git.openstack.org/openstack/mogan
-      - git.openstack.org/openstack/monasca-api
-      - git.openstack.org/openstack/monasca-log-api
-      - git.openstack.org/openstack/monasca-tempest-plugin
-      - git.openstack.org/openstack/murano-tempest-plugin
-      - git.openstack.org/openstack/networking-ansible
-      - git.openstack.org/openstack/networking-bgpvpn
-      - git.openstack.org/openstack/networking-cisco
-      - git.openstack.org/openstack/networking-fortinet
-      - git.openstack.org/openstack/networking-generic-switch
-      - git.openstack.org/openstack/networking-l2gw-tempest-plugin
-      - git.openstack.org/openstack/networking-midonet
-      - git.openstack.org/openstack/networking-sfc
-      - git.openstack.org/openstack/networking-spp
-      - git.openstack.org/openstack/neutron
-      - git.openstack.org/openstack/neutron-dynamic-routing
-      - git.openstack.org/openstack/neutron-fwaas
-      - git.openstack.org/openstack/neutron-lbaas
-      - git.openstack.org/openstack/neutron-tempest-plugin
-      - git.openstack.org/openstack/neutron-vpnaas
-      - git.openstack.org/openstack/nova-lxd
-      - git.openstack.org/openstack/novajoin-tempest-plugin
-      - git.openstack.org/openstack/octavia
-      - git.openstack.org/openstack/octavia-tempest-plugin
-      - git.openstack.org/openstack/oswin-tempest-plugin
-      - git.openstack.org/openstack/panko
-      - git.openstack.org/openstack/patrole
-      - git.openstack.org/openstack/python-watcherclient
-      - git.openstack.org/openstack/qinling
-      - git.openstack.org/openstack/requirements
-      - git.openstack.org/openstack/sahara-tests
-      - git.openstack.org/openstack/senlin
-      - git.openstack.org/openstack/senlin-tempest-plugin
-      - git.openstack.org/openstack/solum-tempest-plugin
-      - git.openstack.org/openstack/tap-as-a-service
-      - git.openstack.org/openstack/telemetry-tempest-plugin
-      - git.openstack.org/openstack/tempest-horizon
-      - git.openstack.org/openstack/tobiko
-      - git.openstack.org/openstack/trio2o
-      - git.openstack.org/openstack/tripleo-common-tempest-plugin
-      - git.openstack.org/openstack/trove-tempest-plugin
-      - git.openstack.org/openstack/valet
-      - git.openstack.org/openstack/vitrage-tempest-plugin
-      - git.openstack.org/openstack/vmware-nsx-tempest-plugin
-      - git.openstack.org/openstack/watcher-tempest-plugin
-      - git.openstack.org/openstack/zaqar-tempest-plugin
-      - git.openstack.org/openstack/zun-tempest-plugin
+      - opendev.org/airship/tempest-plugin
+      - opendev.org/x/almanach
+      - opendev.org/openstack/aodh
+      - opendev.org/openstack/barbican-tempest-plugin
+      - opendev.org/openstack/blazar-tempest-plugin
+      - opendev.org/openstack/ceilometer
+      - opendev.org/openstack/cinder-tempest-plugin
+      - opendev.org/openstack/cloudkitty-tempest-plugin
+      - opendev.org/openstack/congress-tempest-plugin
+      - opendev.org/openstack/designate-tempest-plugin
+      - opendev.org/openstack/ec2api-tempest-plugin
+      - opendev.org/openstack/freezer
+      - opendev.org/openstack/freezer-api
+      - opendev.org/openstack/freezer-tempest-plugin
+      - opendev.org/x/gabbi-tempest
+      - opendev.org/x/gce-api
+      - opendev.org/x/glare
+      - opendev.org/openstack/heat-tempest-plugin
+      - opendev.org/x/intel-nfv-ci-tests
+      - opendev.org/openstack/ironic-tempest-plugin
+      - opendev.org/openstack/ironic-inspector
+      - opendev.org/openstack/keystone-tempest-plugin
+      - opendev.org/x/kingbird
+      - opendev.org/openstack/kuryr-tempest-plugin
+      - opendev.org/openstack/magnum
+      - opendev.org/openstack/magnum-tempest-plugin
+      - opendev.org/openstack/manila
+      - opendev.org/openstack/manila-tempest-plugin
+      - opendev.org/openstack/mistral-tempest-plugin
+      - opendev.org/x/mogan
+      - opendev.org/openstack/monasca-api
+      - opendev.org/openstack/monasca-log-api
+      - opendev.org/openstack/monasca-tempest-plugin
+      - opendev.org/openstack/murano-tempest-plugin
+      - opendev.org/x/networking-ansible
+      - opendev.org/openstack/networking-bgpvpn
+      - opendev.org/x/networking-cisco
+      - opendev.org/x/networking-fortinet
+      - opendev.org/openstack/networking-generic-switch
+      - opendev.org/openstack/networking-l2gw-tempest-plugin
+      - opendev.org/openstack/networking-midonet
+      - opendev.org/openstack/networking-sfc
+      - opendev.org/x/networking-spp
+      - opendev.org/openstack/neutron
+      - opendev.org/openstack/neutron-dynamic-routing
+      - opendev.org/openstack/neutron-fwaas
+      - opendev.org/openstack/neutron-lbaas
+      - opendev.org/openstack/neutron-tempest-plugin
+      - opendev.org/openstack/neutron-vpnaas
+      - opendev.org/x/nova-lxd
+      - opendev.org/x/novajoin-tempest-plugin
+      - opendev.org/openstack/octavia
+      - opendev.org/openstack/octavia-tempest-plugin
+      - opendev.org/openstack/oswin-tempest-plugin
+      - opendev.org/openstack/panko
+      - opendev.org/openstack/patrole
+      - opendev.org/openstack/python-watcherclient
+      - opendev.org/openstack/qinling
+      - opendev.org/openstack/requirements
+      - opendev.org/openstack/sahara-tests
+      - opendev.org/openstack/senlin
+      - opendev.org/openstack/senlin-tempest-plugin
+      - opendev.org/openstack/solum-tempest-plugin
+      - opendev.org/x/tap-as-a-service
+      - opendev.org/openstack/telemetry-tempest-plugin
+      - opendev.org/openstack/tempest-horizon
+      - opendev.org/x/tobiko
+      - opendev.org/x/trio2o
+      - opendev.org/openstack/tripleo-common-tempest-plugin
+      - opendev.org/openstack/trove-tempest-plugin
+      - opendev.org/x/valet
+      - opendev.org/openstack/vitrage-tempest-plugin
+      - opendev.org/x/vmware-nsx-tempest-plugin
+      - opendev.org/openstack/watcher-tempest-plugin
+      - opendev.org/openstack/zaqar-tempest-plugin
+      - opendev.org/openstack/zun-tempest-plugin
 
 - job:
     name: tempest-cinder-v2-api
@@ -510,7 +510,6 @@
         - tempest-full-parallel:
             # Define list of irrelevant files to use everywhere else
             irrelevant-files: &tempest-irrelevant-files
-              - ^(test-|)requirements.txt$
               - ^.*\.rst$
               - ^doc/.*$
               - ^etc/.*$
@@ -544,7 +543,6 @@
             irrelevant-files: *tempest-irrelevant-files
         - tempest-tox-plugin-sanity-check:
             irrelevant-files:
-              - ^(test-|)requirements.txt$
               - ^.*\.rst$
               - ^doc/.*$
               - ^etc/.*$
diff --git a/releasenotes/notes/add-unstable_test-decorator-a73cf97d4ffcc796.yaml b/releasenotes/notes/add-unstable_test-decorator-a73cf97d4ffcc796.yaml
new file mode 100644
index 0000000..2203fd1
--- /dev/null
+++ b/releasenotes/notes/add-unstable_test-decorator-a73cf97d4ffcc796.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    New decorator ``unstable_test`` is added to ``tempest.lib.decorators``.
+    It can be used to mark some test as unstable thus it will be still run
+    by tempest but job will not fail if this test will fail. Such test will
+    be skipped in case of failure.
+    It can be used for example when there is known bug related which cause
+    irregular tests failures. Marking such test as unstable will help other
+    developers to get their job done and still run this test to get additional
+    debug data or to confirm if some potential fix really solved the issue.
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index cc83c04..371b506 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -142,6 +142,12 @@
         if not CONF.compute_feature_enabled.volume_multiattach:
             raise cls.skipException('Volume multi-attach is not available.')
 
+    @classmethod
+    def setup_clients(cls):
+        super(TestMultiAttachVolumeSwap, cls).setup_clients()
+        # Need this to set readonly volumes.
+        cls.admin_volumes_client = cls.os_admin.volumes_client_latest
+
     # NOTE(mriedem): This is an uncommon scenario to call the compute API
     # to swap volumes directly; swap volume is primarily only for volume
     # live migration and retype callbacks from the volume service, and is slow
@@ -162,6 +168,13 @@
         # volumes cleanup can happen successfully irrespective of which volume
         # is attached to server.
         volume1 = self.create_volume(multiattach=True)
+        # Make volume1 read-only since you can't swap from a volume with
+        # multiple read/write attachments, and you can't change the readonly
+        # flag on an in-use volume so we have to do this before attaching
+        # volume1 to anything. If the compute API ever supports per-attachment
+        # attach modes, then we can handle this differently.
+        self.admin_volumes_client.update_volume_readonly(
+            volume1['id'], readonly=True)
         volume2 = self.create_volume(multiattach=True)
 
         # Create two servers and wait for them to be ACTIVE.
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index b67de95..5ba4c9f 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -25,6 +25,10 @@
 
 
 class RolesV3TestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def resource_setup(cls):
diff --git a/tempest/api/volume/admin/test_volume_retype.py b/tempest/api/volume/admin/test_volume_retype.py
index 1c56eb2..9136139 100644
--- a/tempest/api/volume/admin/test_volume_retype.py
+++ b/tempest/api/volume/admin/test_volume_retype.py
@@ -36,7 +36,7 @@
         # process is finished.
         fetched_list = self.admin_volume_client.list_volumes(
             params={'all_tenants': True,
-                    'display_name': vol['name']})['volumes']
+                    'name': vol['name']})['volumes']
 
         for fetched_vol in fetched_list:
             if fetched_vol['id'] != vol['id']:
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index c178272..6ce5d3e 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -50,6 +50,7 @@
                                                 'available')
         return restored_volume
 
+    @decorators.skip_because(bug="1483434")
     @testtools.skipIf(CONF.volume.storage_protocol == 'ceph',
                       'ceph does not support arbitrary container names')
     @decorators.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 3aed4e8..104958a 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -158,7 +158,7 @@
             try:
                 client.delete_snapshot(snap['id'])
             except Exception:
-                LOG.exception("Delete Snapshot exception.")
+                LOG.exception("Delete Snapshot %s exception.", snap['id'])
 
     def dry_run(self):
         snaps = self.list()
@@ -195,7 +195,7 @@
             try:
                 client.delete_server(server['id'])
             except Exception:
-                LOG.exception("Delete Server exception.")
+                LOG.exception("Delete Server %s exception.", server['id'])
 
     def dry_run(self):
         servers = self.list()
@@ -227,7 +227,7 @@
             try:
                 client.delete_server_group(sg['id'])
             except Exception:
-                LOG.exception("Delete Server Group exception.")
+                LOG.exception("Delete Server Group %s exception.", sg['id'])
 
     def dry_run(self):
         sgs = self.list()
@@ -260,11 +260,11 @@
         client = self.client
         keypairs = self.list()
         for k in keypairs:
+            name = k['keypair']['name']
             try:
-                name = k['keypair']['name']
                 client.delete_keypair(name)
             except Exception:
-                LOG.exception("Delete Keypairs exception.")
+                LOG.exception("Delete Keypair %s exception.", name)
 
     def dry_run(self):
         keypairs = self.list()
@@ -300,7 +300,7 @@
             try:
                 client.delete_volume(v['id'])
             except Exception:
-                LOG.exception("Delete Volume exception.")
+                LOG.exception("Delete Volume %s exception.", v['id'])
 
     def dry_run(self):
         vols = self.list()
@@ -323,7 +323,8 @@
         try:
             client.delete_quota_set(self.project_id)
         except Exception:
-            LOG.exception("Delete Volume Quotas exception.")
+            LOG.exception("Delete Volume Quotas exception for 'project %s'.",
+                          self.project_id)
 
     def dry_run(self):
         quotas = self.client.show_quota_set(
@@ -342,7 +343,8 @@
         try:
             client.delete_quota_set(self.project_id)
         except Exception:
-            LOG.exception("Delete Quotas exception.")
+            LOG.exception("Delete Quotas exception for 'project %s'.",
+                          self.project_id)
 
     def dry_run(self):
         client = self.limits_client
@@ -397,7 +399,7 @@
             try:
                 client.delete_network(n['id'])
             except Exception:
-                LOG.exception("Delete Network exception.")
+                LOG.exception("Delete Network %s exception.", n['id'])
 
     def dry_run(self):
         networks = self.list()
@@ -431,7 +433,8 @@
             try:
                 client.delete_floatingip(flip['id'])
             except Exception:
-                LOG.exception("Delete Network Floating IP exception.")
+                LOG.exception("Delete Network Floating IP %s exception.",
+                              flip['id'])
 
     def dry_run(self):
         flips = self.list()
@@ -467,16 +470,20 @@
         ports_client = self.ports_client
         routers = self.list()
         for router in routers:
-            try:
-                rid = router['id']
-                ports = [port for port
-                         in ports_client.list_ports(device_id=rid)['ports']
-                         if net_info.is_router_interface_port(port)]
-                for port in ports:
+            rid = router['id']
+            ports = [port for port
+                     in ports_client.list_ports(device_id=rid)['ports']
+                     if net_info.is_router_interface_port(port)]
+            for port in ports:
+                try:
                     client.remove_router_interface(rid, port_id=port['id'])
+                except Exception:
+                    LOG.exception("Delete Router Interface exception for "
+                                  "'port %s' of 'router %s'.", port['id'], rid)
+            try:
                 client.delete_router(rid)
             except Exception:
-                LOG.exception("Delete Router exception.")
+                LOG.exception("Delete Router %s exception.", rid)
 
     def dry_run(self):
         routers = self.list()
@@ -511,7 +518,8 @@
             try:
                 client.delete_metering_label_rule(rule['id'])
             except Exception:
-                LOG.exception("Delete Metering Label Rule exception.")
+                LOG.exception("Delete Metering Label Rule %s exception.",
+                              rule['id'])
 
     def dry_run(self):
         rules = self.list()
@@ -546,7 +554,8 @@
             try:
                 client.delete_metering_label(label['id'])
             except Exception:
-                LOG.exception("Delete Metering Label exception.")
+                LOG.exception("Delete Metering Label %s exception.",
+                              label['id'])
 
     def dry_run(self):
         labels = self.list()
@@ -585,7 +594,7 @@
             try:
                 client.delete_port(port['id'])
             except Exception:
-                LOG.exception("Delete Port exception.")
+                LOG.exception("Delete Port %s exception.", port['id'])
 
     def dry_run(self):
         ports = self.list()
@@ -626,7 +635,8 @@
             try:
                 client.delete_security_group(secgroup['id'])
             except Exception:
-                LOG.exception("Delete security_group exception.")
+                LOG.exception("Delete security_group %s exception.",
+                              secgroup['id'])
 
     def dry_run(self):
         secgroups = self.list()
@@ -661,7 +671,7 @@
             try:
                 client.delete_subnet(subnet['id'])
             except Exception:
-                LOG.exception("Delete Subnet exception.")
+                LOG.exception("Delete Subnet %s exception.", subnet['id'])
 
     def dry_run(self):
         subnets = self.list()
@@ -696,7 +706,7 @@
             try:
                 client.delete_subnetpool(pool['id'])
             except Exception:
-                LOG.exception("Delete Subnet Pool exception.")
+                LOG.exception("Delete Subnet Pool %s exception.", pool['id'])
 
     def dry_run(self):
         pools = self.list()
@@ -736,7 +746,7 @@
             try:
                 client.delete_flavor(flavor['id'])
             except Exception:
-                LOG.exception("Delete Flavor exception.")
+                LOG.exception("Delete Flavor %s exception.", flavor['id'])
 
     def dry_run(self):
         flavors = self.list()
@@ -773,7 +783,7 @@
             try:
                 client.delete_image(image['id'])
             except Exception:
-                LOG.exception("Delete Image exception.")
+                LOG.exception("Delete Image %s exception.", image['id'])
 
     def dry_run(self):
         images = self.list()
@@ -816,7 +826,7 @@
             try:
                 self.client.delete_user(user['id'])
             except Exception:
-                LOG.exception("Delete User exception.")
+                LOG.exception("Delete User %s exception.", user['id'])
 
     def dry_run(self):
         users = self.list()
@@ -856,7 +866,7 @@
             try:
                 self.client.delete_role(role['id'])
             except Exception:
-                LOG.exception("Delete Role exception.")
+                LOG.exception("Delete Role %s exception.", role['id'])
 
     def dry_run(self):
         roles = self.list()
@@ -898,7 +908,7 @@
             try:
                 self.client.delete_project(project['id'])
             except Exception:
-                LOG.exception("Delete project exception.")
+                LOG.exception("Delete project %s exception.", project['id'])
 
     def dry_run(self):
         projects = self.list()
@@ -935,7 +945,7 @@
                 client.update_domain(domain['id'], enabled=False)
                 client.delete_domain(domain['id'])
             except Exception:
-                LOG.exception("Delete Domain exception.")
+                LOG.exception("Delete Domain %s exception.", domain['id'])
 
     def dry_run(self):
         domains = self.list()
diff --git a/tempest/lib/common/api_version_utils.py b/tempest/lib/common/api_version_utils.py
index 709c319..d29362d 100644
--- a/tempest/lib/common/api_version_utils.py
+++ b/tempest/lib/common/api_version_utils.py
@@ -12,6 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import six
 import testtools
 
 from tempest.lib.common import api_version_request
@@ -108,10 +109,12 @@
 
     :param api_microversion_header_name: Microversion header name
             Example- "X-OpenStack-Nova-API-Version"
-    :param api_microversion: Microversion number like "2.10"
+    :param api_microversion: Microversion number like "2.10", type str.
     :param response_header: Response header where microversion is
             expected to be present.
     """
+    if not isinstance(api_microversion, six.string_types):
+        raise TypeError('api_microversion must be a string')
     api_microversion_header_name = api_microversion_header_name.lower()
     if (api_microversion_header_name not in response_header or
         api_microversion != response_header[api_microversion_header_name]):
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index 4064401..808e0fb 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -154,3 +154,45 @@
         return f
 
     return decorator
+
+
+def unstable_test(*args, **kwargs):
+    """A decorator useful to run tests hitting known bugs and skip it if fails
+
+    This decorator can be used in cases like:
+
+    * We have skipped tests with some bug and now bug is claimed to be fixed.
+      Now we want to check the test stability so we use this decorator.
+      The number of skipped cases with that bug can be counted to mark test
+      stable again.
+    * There is test which is failing often, but not always. If there is known
+      bug related to it, and someone is working on fix, this decorator can be
+      used instead of "skip_because". That will ensure that test is still run
+      so new debug data can be collected from jobs' logs but it will not make
+      life of other developers harder by forcing them to recheck jobs more
+      often.
+
+    ``bug`` must be a number for the test to skip.
+
+    :param bug: bug number causing the test to skip (launchpad or storyboard)
+    :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
+    :raises: testtools.TestCase.skipException if test actually fails,
+        and ``bug`` is included
+    """
+    def decor(f):
+        @functools.wraps(f)
+        def inner(self, *func_args, **func_kwargs):
+            try:
+                return f(self, *func_args, **func_kwargs)
+            except Exception as e:
+                if "bug" in kwargs:
+                    bug = kwargs['bug']
+                    bug_type = kwargs.get('bug_type', 'launchpad')
+                    bug_url = _get_bug_url(bug, bug_type)
+                    msg = ("Marked as unstable and skipped because of bug: "
+                           "%s, failure was: %s") % (bug_url, e)
+                    raise testtools.TestCase.skipException(msg)
+                else:
+                    raise e
+        return inner
+    return decor
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index d2fd021..d09f20c 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -324,6 +324,25 @@
             snapshot['id'])['snapshot']
         return snapshot
 
+    def _cleanup_volume_type(self, volume_type):
+        """Clean up a given volume type.
+
+        Ensuring all volumes associated to a type are first removed before
+        attempting to remove the type itself. This includes any image volume
+        cache volumes stored in a separate tenant to the original volumes
+        created from the type.
+        """
+        admin_volume_type_client = self.os_admin.volume_types_client_latest
+        admin_volumes_client = self.os_admin.volumes_client_latest
+        volumes = admin_volumes_client.list_volumes(
+            detail=True, params={'all_tenants': 1})['volumes']
+        type_name = volume_type['name']
+        for volume in [v for v in volumes if v['volume_type'] == type_name]:
+            test_utils.call_and_ignore_notfound_exc(
+                admin_volumes_client.delete_volume, volume['id'])
+            admin_volumes_client.wait_for_resource_deletion(volume['id'])
+        admin_volume_type_client.delete_volume_type(volume_type['id'])
+
     def create_volume_type(self, client=None, name=None, backend_name=None):
         if not client:
             client = self.os_admin.volume_types_client_latest
@@ -338,7 +357,7 @@
 
         volume_type = client.create_volume_type(
             name=randomized_name, extra_specs=extra_specs)['volume_type']
-        self.addCleanup(client.delete_volume_type, volume_type['id'])
+        self.addCleanup(self._cleanup_volume_type, volume_type)
         return volume_type
 
     def _create_loginable_secgroup_rule(self, secgroup_id=None):
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index 3e6160e..9c6cac7 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -13,7 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import abc
+
 import mock
+import six
 import testtools
 
 from tempest.lib import base as test
@@ -66,9 +69,36 @@
                                condition=True)
 
 
-class TestSkipBecauseDecorator(base.TestCase):
-    def _test_skip_because_helper(self, expected_to_skip=True,
-                                  **decorator_args):
+@six.add_metaclass(abc.ABCMeta)
+class BaseSkipDecoratorTests(object):
+
+    @abc.abstractmethod
+    def _test_skip_helper(self, raise_exception=True, expected_to_skip=True,
+                          **decorator_args):
+        return
+
+    def test_skip_launchpad_bug(self):
+        self._test_skip_helper(bug='12345')
+
+    def test_skip_storyboard_bug(self):
+        self._test_skip_helper(bug='1992', bug_type='storyboard')
+
+    def test_skip_bug_without_bug_never_skips(self):
+        """Never skip without a bug parameter."""
+        self._test_skip_helper(
+            raise_exception=False, expected_to_skip=False, condition=True)
+        self._test_skip_helper(
+            raise_exception=False, expected_to_skip=False)
+
+    def test_skip_invalid_bug_number(self):
+        """Raise InvalidParam if with an invalid bug number"""
+        self.assertRaises(lib_exc.InvalidParam, self._test_skip_helper,
+                          bug='critical_bug')
+
+
+class TestSkipBecauseDecorator(base.TestCase, BaseSkipDecoratorTests):
+    def _test_skip_helper(self, raise_exception=True, expected_to_skip=True,
+                          **decorator_args):
         class TestFoo(test.BaseTestCase):
             _interface = 'json'
 
@@ -90,38 +120,56 @@
             # assert that test_bar returned 0
             self.assertEqual(TestFoo('test_bar').test_bar(), 0)
 
-    def test_skip_because_launchpad_bug(self):
-        self._test_skip_because_helper(bug='12345')
-
     def test_skip_because_launchpad_bug_and_condition_true(self):
-        self._test_skip_because_helper(bug='12348', condition=True)
+        self._test_skip_helper(bug='12348', condition=True)
 
     def test_skip_because_launchpad_bug_and_condition_false(self):
-        self._test_skip_because_helper(expected_to_skip=False,
-                                       bug='12349', condition=False)
-
-    def test_skip_because_storyboard_bug(self):
-        self._test_skip_because_helper(bug='1992', bug_type='storyboard')
-
-    def test_skip_because_storyboard_bug_and_condition_true(self):
-        self._test_skip_because_helper(bug='1992', bug_type='storyboard',
-                                       condition=True)
+        self._test_skip_helper(expected_to_skip=False,
+                               bug='12349', condition=False)
 
     def test_skip_because_storyboard_bug_and_condition_false(self):
-        self._test_skip_because_helper(expected_to_skip=False,
-                                       bug='1992', bug_type='storyboard',
-                                       condition=False)
+        self._test_skip_helper(expected_to_skip=False,
+                               bug='1992', bug_type='storyboard',
+                               condition=False)
 
-    def test_skip_because_bug_without_bug_never_skips(self):
-        """Never skip without a bug parameter."""
-        self._test_skip_because_helper(expected_to_skip=False,
-                                       condition=True)
-        self._test_skip_because_helper(expected_to_skip=False)
+    def test_skip_because_storyboard_bug_and_condition_true(self):
+        self._test_skip_helper(bug='1992', bug_type='storyboard',
+                               condition=True)
 
-    def test_skip_because_invalid_bug_number(self):
-        """Raise InvalidParam if with an invalid bug number"""
-        self.assertRaises(lib_exc.InvalidParam, self._test_skip_because_helper,
-                          bug='critical_bug')
+
+class TestUnstableTestDecorator(base.TestCase, BaseSkipDecoratorTests):
+
+    def _test_skip_helper(self, raise_exception=True, expected_to_skip=True,
+                          **decorator_args):
+        fail_test_reason = "test_bar failed"
+
+        class TestFoo(test.BaseTestCase):
+
+            @decorators.unstable_test(**decorator_args)
+            def test_bar(self):
+                if raise_exception:
+                    raise Exception(fail_test_reason)
+                else:
+                    return 0
+
+        t = TestFoo('test_bar')
+        if expected_to_skip:
+            e = self.assertRaises(testtools.TestCase.skipException, t.test_bar)
+            bug = decorator_args['bug']
+            bug_type = decorator_args.get('bug_type', 'launchpad')
+            self.assertRegex(
+                str(e),
+                r'Marked as unstable and skipped because of bug\: %s.*, '
+                'failure was: %s' % (decorators._get_bug_url(bug, bug_type),
+                                     fail_test_reason)
+            )
+        else:
+            # assert that test_bar returned 0
+            self.assertEqual(TestFoo('test_bar').test_bar(), 0)
+
+    def test_skip_bug_given_exception_not_raised(self):
+        self._test_skip_helper(raise_exception=False, expected_to_skip=False,
+                               bug='1234')
 
 
 class TestIdempotentIdDecorator(base.TestCase):