Merge "Test image formats"
diff --git a/requirements.txt b/requirements.txt
index 6e66046..b0df18b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -23,3 +23,4 @@
 debtcollector>=1.2.0 # Apache-2.0
 defusedxml>=0.7.1 # PSFL
 fasteners>=0.16.0 # Apache-2.0
+testscenarios>=0.5.0
diff --git a/tempest/api/image/v2/test_images_formats.py b/tempest/api/image/v2/test_images_formats.py
new file mode 100644
index 0000000..398bc2d
--- /dev/null
+++ b/tempest/api/image/v2/test_images_formats.py
@@ -0,0 +1,88 @@
+# Copyright 2024 Red Hat, 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.
+
+import os
+
+import testscenarios
+import yaml
+
+from tempest.api.image import base
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+CONF = config.CONF
+
+
+def load_tests(loader, suite, pattern):
+    """Generate scenarios from the image manifest."""
+    if CONF.image.images_manifest_file is None:
+        return suite
+    ImagesFormatTest.scenarios = []
+    with open(CONF.image.images_manifest_file) as f:
+        ImagesFormatTest._manifest = yaml.load(f, Loader=yaml.SafeLoader)
+        for imgdef in ImagesFormatTest._manifest['images']:
+            ImagesFormatTest.scenarios.append((imgdef['name'],
+                                               {'imgdef': imgdef}))
+    result = loader.suiteClass()
+    result.addTests(testscenarios.generate_scenarios(suite))
+    return result
+
+
+class ImagesFormatTest(base.BaseV2ImageTest):
+    def setUp(self):
+        super().setUp()
+        if CONF.image.images_manifest_file is None:
+            self.skipTest('Image format testing is not configured')
+        self._image_base = os.path.dirname(os.path.abspath(
+            CONF.image.images_manifest_file))
+
+        self.images = []
+
+    def tearDown(self):
+        for img in self.images:
+            try:
+                self.client.delete_image(img['id'])
+            except lib_exc.NotFound:
+                pass
+        return super().tearDown()
+
+    def _test_image(self, image_def, override_format=None):
+        image_name = data_utils.rand_name(
+            prefix=CONF.resource_name_prefix,
+            name=image_def['name'])
+        image = self.client.create_image(
+            name=image_name,
+            container_format='bare',
+            disk_format=override_format or image_def['format'])
+        self.images.append(image)
+        image_fn = os.path.join(self._image_base, image_def['filename'])
+        with open(image_fn, 'rb') as f:
+            self.client.store_image_file(image['id'], f)
+
+    @decorators.idempotent_id('a245fcbe-63ce-4dc1-a1d0-c16d76d9e6df')
+    def test_accept_usable_formats(self):
+        if self.imgdef['usable']:
+            if self.imgdef['format'] in CONF.image.disk_formats:
+                # These are expected to work
+                self._test_image(self.imgdef)
+            else:
+                # If this is not configured to be supported, we should get
+                # a BadRequest from glance
+                self.assertRaises(lib_exc.BadRequest,
+                                  self._test_image, self.imgdef)
+        else:
+            self.skipTest(
+                'Glance does not currently reject unusable images on upload')
diff --git a/tempest/config.py b/tempest/config.py
index 0fcc71c..021875e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -684,7 +684,11 @@
                 default=['qcow2', 'raw', 'ami', 'ari', 'aki', 'vhd', 'vmdk',
                          'vdi', 'iso', 'vhdx'],
                 help="A list of image's disk formats "
-                     "users can specify.")
+                     "users can specify."),
+    cfg.StrOpt('images_manifest_file',
+               default=None,
+               help="A path to a manifest.yml generated using the "
+                    "os-test-images project"),
 ]
 
 image_feature_group = cfg.OptGroup(name='image-feature-enabled',