isos: Check maximum expected size

This patch allows the configuration to express maximum expected size for
ISOs created in createiso and extra_isos phases. If the image is larger
than this limit, a warning is emitted in test phase. The compose itself
is not affected in any way.

JIRA: COMPOSE-2824
Signed-off-by: Lubomír Sedlář <lsedlar@redhat.com>
This commit is contained in:
Lubomír Sedlář 2019-02-12 12:09:08 +01:00
parent 7693e562b1
commit 6c6d4759f5
8 changed files with 150 additions and 4 deletions

View File

@ -1003,6 +1003,12 @@ Options
**createiso_skip** = False **createiso_skip** = False
(*list*) -- mapping that defines which variants and arches to skip during createiso; format: [(variant_uid_regex, {arch|*: True})] (*list*) -- mapping that defines which variants and arches to skip during createiso; format: [(variant_uid_regex, {arch|*: True})]
**createiso_max_size**
(*list*) -- mapping that defines maximum expected size for each variant and
arch. If the ISO is larger than the limit, a warning will be issued.
Format: ``[(variant_uid_regex, {arch|*: number})]``
**create_jigdo** = True **create_jigdo** = True
(*bool*) -- controls the creation of jigdo from ISO (*bool*) -- controls the creation of jigdo from ISO
@ -1574,6 +1580,9 @@ will reuse boot configuration from that variant.
are ignored. If you want to include them in the ISO, set this option to are ignored. If you want to include them in the ISO, set this option to
``True``. ``True``.
* ``max_size`` -- (*int*) expected maximum size in bytes. If the final
image is larger, a warning will be issued.
Example config Example config
-------------- --------------
:: ::

View File

@ -730,6 +730,7 @@ def make_schema():
"default": {}, "default": {},
}, },
"createiso_skip": _variant_arch_mapping({"type": "boolean"}), "createiso_skip": _variant_arch_mapping({"type": "boolean"}),
"createiso_max_size": _variant_arch_mapping({"type": "number"}),
"createiso_break_hardlinks": { "createiso_break_hardlinks": {
"type": "boolean", "type": "boolean",
"default": False, "default": False,
@ -969,6 +970,7 @@ def make_schema():
"type": "boolean", "type": "boolean",
"default": False, "default": False,
}, },
"max_size": {"type": "number"},
}, },
"required": ["include_variants"], "required": ["include_variants"],
"additionalProperties": False "additionalProperties": False

View File

@ -263,6 +263,7 @@ def add_iso_to_metadata(
compose.im.add(variant.uid, variant_arch, img) compose.im.add(variant.uid, variant_arch, img)
else: else:
compose.im.add(variant.uid, arch, img) compose.im.add(variant.uid, arch, img)
return img
def run_createiso_command(runroot, num, compose, bootable, arch, cmd, mounts, def run_createiso_command(runroot, num, compose, bootable, arch, cmd, mounts,

View File

@ -125,7 +125,7 @@ class ExtraIsosThread(WorkerThread):
arch, "extraiso-%s" % os.path.basename(iso_path)), arch, "extraiso-%s" % os.path.basename(iso_path)),
with_jigdo=compose.conf['create_jigdo']) with_jigdo=compose.conf['create_jigdo'])
add_iso_to_metadata( img = add_iso_to_metadata(
compose, compose,
variant, variant,
arch, arch,
@ -133,6 +133,7 @@ class ExtraIsosThread(WorkerThread):
bootable, bootable,
additional_variants=config["include_variants"], additional_variants=config["include_variants"],
) )
img._max_size = config.get("max_size")
self.pool.log_info("[DONE ] %s" % msg) self.pool.log_info("[DONE ] %s" % msg)

View File

@ -114,10 +114,11 @@ def check_image_sanity(compose):
if arch not in im.images[variant.uid]: if arch not in im.images[variant.uid]:
continue continue
for img in im.images[variant.uid][arch]: for img in im.images[variant.uid][arch]:
check(compose, variant, arch, img) check_sanity(compose, variant, arch, img)
check_size_limit(compose, variant, arch, img)
def check(compose, variant, arch, image): def check_sanity(compose, variant, arch, image):
path = os.path.join(compose.paths.compose.topdir(), image.path) path = os.path.join(compose.paths.compose.topdir(), image.path)
deliverable = getattr(image, 'deliverable') deliverable = getattr(image, 'deliverable')
can_fail = getattr(image, 'can_fail', False) can_fail = getattr(image, 'can_fail', False)
@ -159,3 +160,22 @@ def has_gpt(f):
def has_eltorito(f): def has_eltorito(f):
return _check_magic(f, 0x8801, b'CD001\1EL TORITO SPECIFICATION') return _check_magic(f, 0x8801, b'CD001\1EL TORITO SPECIFICATION')
def check_size_limit(compose, variant, arch, img):
"""If a size of the ISO image is over the configured limit, report a
warning. Do nothing for other types of images.
"""
if img.format != "iso":
return
limits = get_arch_variant_data(compose.conf, "createiso_max_size", arch, variant)
if not limits and not getattr(img, "_max_size", None):
return
# For ISOs created in extra_isos phase we add an attribute with the limit,
# and there is a global option otherwise.
limit = getattr(img, "_max_size", None) or limits[0]
if img.size > limit:
compose.log_warning(
"ISO %s is too big. Expected max %dB, got %dB" % (img.path, limit, img.size)
)

View File

@ -169,7 +169,9 @@ class DummyCompose(object):
self.log_debug = mock.Mock() self.log_debug = mock.Mock()
self.log_warning = mock.Mock() self.log_warning = mock.Mock()
self.get_image_name = mock.Mock(return_value='image-name') self.get_image_name = mock.Mock(return_value='image-name')
self.image = mock.Mock(path='Client/i386/iso/image.iso', can_fail=False, size=123) self.image = mock.Mock(
path='Client/i386/iso/image.iso', can_fail=False, size=123, _max_size=None,
)
self.im = mock.Mock(images={'Client': {'amd64': [self.image]}}) self.im = mock.Mock(images={'Client': {'amd64': [self.image]}})
self.old_composes = [] self.old_composes = []
self.config_dir = '/home/releng/config' self.config_dir = '/home/releng/config'

View File

@ -220,6 +220,63 @@ class ExtraIsosThreadTest(helpers.PungiTestCase):
) )
self.assertEqual(pmm.call_args_list, [mock.call(compose, server, "x86_64")]) self.assertEqual(pmm.call_args_list, [mock.call(compose, server, "x86_64")])
def test_image_with_max_size(self, aitm, rcc, gef, gic, gfn, gvi, pmm):
compose = helpers.DummyCompose(self.topdir, {
"bootable": True,
"buildinstall_method": "lorax"
})
server = compose.variants["Server"]
cfg = {
"include_variants": ["Client"],
"max_size": 15,
}
gfn.return_value = "my.iso"
gvi.return_value = "my volume id"
gic.return_value = "/tmp/iso-graft-points"
t = extra_isos.ExtraIsosThread(mock.Mock())
with mock.patch("time.sleep"):
t.process((compose, cfg, server, "x86_64"), 1)
self.assertEqual(gfn.call_args_list,
[mock.call(compose, server, "x86_64", None)])
self.assertEqual(gvi.call_args_list,
[mock.call(compose, server, "x86_64", [])])
self.assertEqual(gef.call_args_list,
[mock.call(compose, server, "x86_64", [])])
self.assertEqual(
gic.call_args_list,
[
mock.call(
compose,
server,
"x86_64",
["Client"],
"my.iso",
bootable=True,
inherit_extra_files=False,
),
],
)
self.assertEqual(
rcc.call_args_list,
[mock.call(False, 1, compose, True, "x86_64",
["bash", os.path.join(self.topdir, "work/x86_64/tmp-Server/extraiso-my.iso.sh")],
[self.topdir],
log_file=os.path.join(self.topdir, "logs/x86_64/extraiso-my.iso.x86_64.log"),
with_jigdo=True)]
)
self.assertEqual(
aitm.call_args_list,
[mock.call(compose, server, "x86_64",
os.path.join(self.topdir, "compose/Server/x86_64/iso/my.iso"),
True, additional_variants=["Client"])]
)
self.assertEqual(aitm.return_value._max_size, 15)
self.assertEqual(pmm.call_args_list, [mock.call(compose, server, "x86_64")])
def test_binary_image_custom_naming(self, aitm, rcc, gef, gic, gfn, gvi, pmm): def test_binary_image_custom_naming(self, aitm, rcc, gef, gic, gfn, gvi, pmm):
compose = helpers.DummyCompose(self.topdir, {}) compose = helpers.DummyCompose(self.topdir, {})
server = compose.variants['Server'] server = compose.variants['Server']

View File

@ -166,6 +166,60 @@ class TestCheckImageSanity(PungiTestCase):
except Exception: except Exception:
self.fail('Checking optional variant must not raise') self.fail('Checking optional variant must not raise')
@mock.patch("pungi.phases.test.check_sanity", new=mock.Mock())
def test_too_big_iso(self):
compose = DummyCompose(self.topdir, {"createiso_max_size": [(".*", {"*": 10})]})
compose.image.format = 'iso'
compose.image.bootable = False
compose.image.size = 20
test_phase.check_image_sanity(compose)
warnings = [call[0][0] for call in compose.log_warning.call_args_list]
self.assertIn(
"ISO Client/i386/iso/image.iso is too big. Expected max 10B, got 20B",
warnings,
)
@mock.patch("pungi.phases.test.check_sanity", new=mock.Mock())
def test_too_big_unified(self):
compose = DummyCompose(self.topdir, {})
compose.image.format = 'iso'
compose.image.bootable = False
compose.image.size = 20
compose.image.unified = True
setattr(compose.image, "_max_size", 10)
test_phase.check_image_sanity(compose)
warnings = [call[0][0] for call in compose.log_warning.call_args_list]
self.assertIn(
"ISO Client/i386/iso/image.iso is too big. Expected max 10B, got 20B",
warnings,
)
@mock.patch("pungi.phases.test.check_sanity", new=mock.Mock())
def test_fits_in_limit(self):
compose = DummyCompose(self.topdir, {"createiso_max_size": [(".*", {"*": 20})]})
compose.image.format = 'iso'
compose.image.bootable = False
compose.image.size = 5
test_phase.check_image_sanity(compose)
self.assertEqual(compose.log_warning.call_args_list, [])
@mock.patch("pungi.phases.test.check_sanity", new=mock.Mock())
def test_non_iso(self):
compose = DummyCompose(self.topdir, {"createiso_max_size": [(".*", {"*": 10})]})
compose.image.format = 'qcow2'
compose.image.bootable = False
compose.image.size = 20
test_phase.check_image_sanity(compose)
self.assertEqual(compose.log_warning.call_args_list, [])
class TestRepoclosure(PungiTestCase): class TestRepoclosure(PungiTestCase):