Source code for image.v2.test_images_formats
# 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.compute import base as compute_base
from tempest.api.image import base
from tempest.common import waiters
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
CONF = config.CONF
[docs]
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
[docs]
class ImagesFormatTest(base.BaseV2ImageTest,
compute_base.BaseV2ComputeTest):
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()
@classmethod
def resource_setup(cls):
super().resource_setup()
cls.available_import_methods = cls.client.info_import()[
'import-methods']['value']
def _test_image(self, image_def, override_format=None, asimport=False):
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:
if asimport:
self.client.stage_image_file(image['id'], f)
self.client.image_import(image['id'], method='glance-direct')
else:
self.client.store_image_file(image['id'], f)
return image
[docs]
@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')
[docs]
@decorators.idempotent_id('7c7c2f16-2e97-4dce-8cb4-bc10be031c85')
def test_accept_reject_formats_import(self):
"""Make sure glance rejects invalid images during conversion."""
if 'glance-direct' not in self.available_import_methods:
self.skipTest('Import via glance-direct is not available')
if not CONF.image_feature_enabled.image_conversion:
self.skipTest('Import image_conversion not enabled')
# VMDK with footer was not supported by earlier service versions,
# so we need to tolerate it passing and failing (skip for the latter).
# See this for more info:
# https://bugs.launchpad.net/glance/+bug/2073262
is_broken = 'footer' in self.imgdef['name']
if (self.imgdef['format'] in CONF.image.disk_formats and
self.imgdef['usable']):
# Usable images should end up in active state
image = self._test_image(self.imgdef, asimport=True)
try:
waiters.wait_for_image_status(self.client, image['id'],
'active')
except lib_exc.TimeoutException:
if is_broken:
self.skipTest(
'Older glance did not support vmdk-with-footer')
else:
raise
else:
# FIXME(danms): Make this better, but gpt will fail before
# the import even starts until glance has it in its API
# schema as a valid value. Other formats expected to fail
# do so during import and return to queued state.
if self.imgdef['format'] not in CONF.image.disk_formats:
self.assertRaises(lib_exc.BadRequest,
self._test_image,
self.imgdef, asimport=True)
else:
image = self._test_image(self.imgdef, asimport=True)
waiters.wait_for_image_status(self.client, image['id'],
'queued')
self.client.delete_image(image['id'])
if self.imgdef['format'] == 'iso':
# NOTE(danms): Glance has a special case to not convert ISO images
# because they are special and must remain as ISOs in order to be
# properly used for CD-based rescue and boot.
self.assertEqual('iso', image['disk_format'])
def _create_server_with_image_def(self, image_def, **overrides):
image_def = dict(image_def, **overrides)
image = self._test_image(image_def)
server = self.create_test_server(name='server-%s' % image['name'],
image_id=image['id'],
wait_until='ACTIVE')
return server
[docs]
@decorators.idempotent_id('f77394bc-81f4-4d54-9f5b-e48f3d6b5376')
def test_compute_rejects_invalid(self):
"""Make sure compute rejects invalid/insecure images."""
if self.imgdef['format'] not in CONF.image.disk_formats:
# if this format is not allowed by glance, we can not create
# a properly-formatted image for it, so skip it.
self.skipTest(
'Format %s not allowed by config' % self.imgdef['format'])
if CONF.image_feature_enabled.image_format_enforcement:
# If glance rejects bad images during upload, we cannot get them
# registered so that we can test nova.
self.skipTest(
'Unable to test compute image formats if glance does not '
'allow them to be uploaded')
# VMDK with footer was not supported by earlier service versions,
# so we need to tolerate it passing and failing (skip for the latter).
# See this for more info:
# https://bugs.launchpad.net/glance/+bug/2073262
is_broken = 'footer' in self.imgdef['name']
if self.imgdef['usable']:
try:
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')
else:
raise
self.delete_server(server['id'])
else:
self.assertRaises(exceptions.BuildErrorException,
self._create_server_with_image_def,
self.imgdef)
[docs]
@decorators.idempotent_id('ffe21610-e801-4992-9b81-e2d646e2e2e9')
def test_compute_rejects_format_mismatch(self):
"""Make sure compute rejects any image with a format mismatch."""
if CONF.image_feature_enabled.image_format_enforcement:
# If glance rejects bad images during upload, we cannot get them
# registered so that we can test nova.
self.skipTest(
'Unable to test compute image formats if glance does not '
'allow them to be uploaded')
# Lying about the disk_format should always fail
override_fmt = (
self.imgdef['format'] in ('raw', 'gpt') and 'qcow2' or 'raw')
self.assertRaises(exceptions.BuildErrorException,
self._create_server_with_image_def,
self.imgdef,
format=override_fmt)