Merge "Use project_reader in compute volumes tests"
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
index 3626a3f..0c604cc 100644
--- a/doc/source/write_tests.rst
+++ b/doc/source/write_tests.rst
@@ -83,7 +83,7 @@
           """
           super(TestExampleCase, cls).skip_checks()
           if not CONF.section.foo
-              cls.skip('A helpful message')
+              cls.skipTest('A helpful message')
 
       @classmethod
       def setup_credentials(cls):
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
index 33c141d..29ec4d5 100644
--- a/releasenotes/source/index.rst
+++ b/releasenotes/source/index.rst
@@ -6,6 +6,7 @@
    :maxdepth: 1
 
    unreleased
+   v46.0.0
    v45.0.0
    v44.0.0
    v43.0.0
diff --git a/releasenotes/source/v46.0.0.rst b/releasenotes/source/v46.0.0.rst
new file mode 100644
index 0000000..cbd2e95
--- /dev/null
+++ b/releasenotes/source/v46.0.0.rst
@@ -0,0 +1,6 @@
+=====================
+v46.0.0 Release Notes
+=====================
+
+.. release-notes:: 46.0.0 Release Notes
+   :version: 46.0.0
diff --git a/requirements.txt b/requirements.txt
index a1eff53..eafbe0a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
 cliff!=2.9.0,>=2.8.0 # Apache-2.0
-jsonschema>=3.2.0 # MIT
+jsonschema>=4.5.0 # MIT
 testtools>=2.2.0 # MIT
 paramiko>=2.7.0 # LGPLv2.1+
 cryptography>=2.1 # BSD/Apache-2.0
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 72df5e3..82ddae6 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -482,9 +482,9 @@
             # but cirros (and other) test images won't see the device unless
             # they have lsilogic drivers (which is the default). So use this
             # as sort of the indication that the test should be enabled.
-            self.skip('hw_scsi_model=virtio-scsi not set on image')
+            self.skipTest('hw_scsi_model=virtio-scsi not set on image')
         if not CONF.validation.run_validation:
-            self.skip('validation is required for this test')
+            self.skipTest('validation is required for this test')
 
         validation_resources = self.get_test_validation_resources(
             self.os_primary)
diff --git a/tempest/api/image/v2/test_images_formats.py b/tempest/api/image/v2/test_images_formats.py
index 520a215..6914745 100644
--- a/tempest/api/image/v2/test_images_formats.py
+++ b/tempest/api/image/v2/test_images_formats.py
@@ -196,8 +196,9 @@
                 server = self._create_server_with_image_def(self.imgdef)
             except exceptions.BuildErrorException:
                 if is_broken:
-                    self.skip('Tolerating failed build with known-broken '
-                              'image format')
+                    self.skipTest(
+                        'Tolerating failed build with known-broken image '
+                        'format')
                 else:
                     raise
             self.delete_server(server['id'])
diff --git a/tempest/lib/common/jsonschema_validator.py b/tempest/lib/common/jsonschema_validator.py
index 1618175..e56a81f 100644
--- a/tempest/lib/common/jsonschema_validator.py
+++ b/tempest/lib/common/jsonschema_validator.py
@@ -18,7 +18,7 @@
 
 # JSON Schema validator and format checker used for JSON Schema validation
 JSONSCHEMA_VALIDATOR = jsonschema.Draft4Validator
-FORMAT_CHECKER = jsonschema.draft4_format_checker
+FORMAT_CHECKER = JSONSCHEMA_VALIDATOR.FORMAT_CHECKER
 
 
 # NOTE(gmann): Add customized format checker for 'date-time' format because:
@@ -39,7 +39,7 @@
         return True
 
 
-@jsonschema.FormatChecker.cls_checks('base64')
+@FORMAT_CHECKER.checks('base64')
 def _validate_base64_format(instance):
     try:
         if isinstance(instance, str):
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index f378881..ec220b0 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -169,6 +169,61 @@
         # Step 6: Verify console log
         self.log_console_output([instance])
 
+    @decorators.idempotent_id('3c87bc15-cf6a-4dd6-8c23-4541b2cc3dbb')
+    @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
+                          'Cinder volume snapshots are disabled')
+    @utils.services('compute', 'volume', 'image')
+    def test_bootable_volume_last_snapshot_delete_while_stopped(self):
+        """Test bootable volume snapshot deletion while instance is stopped.
+
+        This test ensures that all volume snapshots can be deleted which
+        verifies that their backing files were handled correctly during
+        deletions.
+
+        This scenario is related to the Cinder NFS backend.
+
+        Steps:
+        1. Create a bootable volume from an image.
+        2. Launch an instance from the created volume.
+        3. Create three volume snapshots of the created volume.
+        4. Stop the instance.
+        5. Delete the latest snapshot and verify it succeeds.
+        6. Delete the next latest snapshot and verify it succeeds.
+        7. Delete the remaining snapshot and verify it succeeds.
+        """
+        # Step 1: Create a bootable volume from an image
+        volume = self.create_volume_from_image()
+
+        # Step 2: Boot an instance from the created volume
+        instance = self.boot_instance_from_resource(
+            source_id=volume['id'],
+            source_type='volume',
+            wait_until='SSHABLE'
+        )
+
+        # Step 3: Create three volume snapshots of the bootable volume.
+        # The force=True is needed in order to snapshot an attached volume.
+        snapshot1 = self.create_volume_snapshot(volume['id'], force=True)
+        snapshot2 = self.create_volume_snapshot(volume['id'], force=True)
+        snapshot3 = self.create_volume_snapshot(volume['id'], force=True)
+
+        # Step 4: Stop the instance
+        self.servers_client.stop_server(instance['id'])
+        waiters.wait_for_server_status(self.servers_client, instance['id'],
+                                       'SHUTOFF')
+
+        # Step 5: Delete the latest (newest) snapshot
+        self.snapshots_client.delete_snapshot(snapshot3['id'])
+        self.snapshots_client.wait_for_resource_deletion(snapshot3['id'])
+
+        # Step 6: Delete the next latest (next newest) snapshot
+        self.snapshots_client.delete_snapshot(snapshot2['id'])
+        self.snapshots_client.wait_for_resource_deletion(snapshot2['id'])
+
+        # Step 7: Delete the last remaining snapshot
+        self.snapshots_client.delete_snapshot(snapshot1['id'])
+        self.snapshots_client.wait_for_resource_deletion(snapshot1['id'])
+
     @decorators.idempotent_id('05795fb2-b2a7-4c9f-8fac-ff25aedb1489')
     @decorators.attr(type='slow')
     @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,