From 636ac7918656b844e915a193dc48dbf7a6d8cf7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubom=C3=ADr=20Sedl=C3=A1=C5=99?= Date: Fri, 26 Feb 2016 10:31:43 +0100 Subject: [PATCH] [buildinstall] Hardlink boot isos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of creating a symlink, try to hardlink the image, and copy it if hardlinking fails. Signed-off-by: Lubomír Sedlář --- pungi/phases/buildinstall.py | 21 ++++++++----- tests/helpers.py | 13 ++++++++ tests/test_buildinstall.py | 60 ++++++++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/pungi/phases/buildinstall.py b/pungi/phases/buildinstall.py index afec31d8..cf51a8ca 100644 --- a/pungi/phases/buildinstall.py +++ b/pungi/phases/buildinstall.py @@ -24,11 +24,12 @@ import shutil import re from kobo.threads import ThreadPool, WorkerThread -from kobo.shortcuts import run, relative_path +from kobo.shortcuts import run from productmd.images import Image from pungi.arch import get_valid_arches from pungi.util import get_buildroot_rpms, get_volid, get_arch_variant_data +from pungi.util import get_file_size, get_mtime from pungi.wrappers.lorax import LoraxWrapper from pungi.wrappers.kojiwrapper import KojiWrapper from pungi.wrappers.iso import IsoWrapper @@ -72,6 +73,11 @@ class BuildinstallPhase(PhaseBase): "expected_types": [str], "optional": True, }, + { + "name": "buildinstall_symlink", + "expected_types": [bool], + "optional": True, + }, ) def __init__(self, compose): @@ -332,9 +338,11 @@ def symlink_boot_iso(compose, arch, variant): return compose.log_info("[BEGIN] %s" % msg) - # can't make a hardlink - possible cross-device link due to 'symlink_to' argument - symlink_target = relative_path(boot_iso_path, new_boot_iso_path) - os.symlink(symlink_target, new_boot_iso_path) + # Try to hardlink, and copy if that fails + try: + os.link(boot_iso_path, new_boot_iso_path) + except OSError: + shutil.copy2(boot_iso_path, new_boot_iso_path) iso = IsoWrapper() implant_md5 = iso.get_implanted_md5(new_boot_iso_path) @@ -345,10 +353,9 @@ def symlink_boot_iso(compose, arch, variant): run(iso.get_manifest_cmd(iso_name), workdir=iso_dir) img = Image(compose.im) - img.implant_md5 = iso.get_implanted_md5(new_boot_iso_path) img.path = new_boot_iso_relative_path - img.mtime = int(os.stat(new_boot_iso_path).st_mtime) - img.size = os.path.getsize(new_boot_iso_path) + img.mtime = get_mtime(new_boot_iso_path) + img.size = get_file_size(new_boot_iso_path) img.arch = arch img.type = "boot" img.format = "iso" diff --git a/tests/helpers.py b/tests/helpers.py index a8bc8f9d..eb574ec5 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import mock +import os import unittest import tempfile import shutil @@ -43,6 +44,7 @@ class DummyCompose(object): 'Everything': mock.Mock(uid='Everything', arches=['x86_64', 'amd64'], type='variant', is_empty=False), } + self.log_info = mock.Mock() self.log_error = mock.Mock() self.log_debug = mock.Mock() self.get_image_name = mock.Mock(return_value='image-name') @@ -61,3 +63,14 @@ class DummyCompose(object): for variant in self.variants.itervalues(): result |= set(variant.arches) return result + + +def touch(path): + """Helper utility that creates an dummy file in given location. Directories + will be created.""" + try: + os.makedirs(os.path.dirname(path)) + except OSError: + pass + with open(path, 'w') as f: + f.write(path + '\n') diff --git a/tests/test_buildinstall.py b/tests/test_buildinstall.py index 5b91b4f7..87c79232 100755 --- a/tests/test_buildinstall.py +++ b/tests/test_buildinstall.py @@ -10,8 +10,8 @@ import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from pungi.phases.buildinstall import BuildinstallPhase, BuildinstallThread -from tests.helpers import DummyCompose, PungiTestCase +from pungi.phases.buildinstall import BuildinstallPhase, BuildinstallThread, symlink_boot_iso +from tests.helpers import DummyCompose, PungiTestCase, touch class BuildInstallCompose(DummyCompose): @@ -534,5 +534,61 @@ class BuildinstallThreadTestCase(PungiTestCase): ]) +class TestSymlinkIso(PungiTestCase): + + def setUp(self): + super(TestSymlinkIso, self).setUp() + self.compose = BuildInstallCompose(self.topdir, {}) + os_tree = self.compose.paths.compose.os_tree('x86_64', self.compose.variants['Server']) + self.boot_iso_path = os.path.join(os_tree, "images", "boot.iso") + touch(self.boot_iso_path) + + @mock.patch('pungi.phases.buildinstall.Image') + @mock.patch('pungi.phases.buildinstall.get_mtime') + @mock.patch('pungi.phases.buildinstall.get_file_size') + @mock.patch('pungi.phases.buildinstall.IsoWrapper') + @mock.patch('pungi.phases.buildinstall.run') + def test_hardlink(self, run, IsoWrapperCls, get_file_size, get_mtime, ImageCls): + self.compose.conf = {'buildinstall_symlink': False} + IsoWrapper = IsoWrapperCls.return_value + get_file_size.return_value = 1024 + get_mtime.return_value = 13579 + + symlink_boot_iso(self.compose, 'x86_64', self.compose.variants['Server']) + + tgt = self.topdir + '/compose/Server/x86_64/iso/image-name' + self.assertTrue(os.path.isfile(tgt)) + self.assertEqual(os.stat(tgt).st_ino, + os.stat(self.topdir + '/compose/Server/x86_64/os/images/boot.iso').st_ino) + + self.assertItemsEqual( + self.compose.get_image_name.mock_calls, + [mock.call('x86_64', self.compose.variants['Server'], + disc_type='boot', disc_num=None, suffix='.iso')]) + self.assertItemsEqual(IsoWrapper.get_implanted_md5.mock_calls, + [mock.call(tgt)]) + self.assertItemsEqual(IsoWrapper.get_manifest_cmd.mock_calls, + [mock.call('image-name')]) + self.assertItemsEqual(IsoWrapper.get_volume_id.mock_calls, + [mock.call(tgt)]) + self.assertItemsEqual(run.mock_calls, + [mock.call(IsoWrapper.get_manifest_cmd.return_value, + workdir=self.topdir + '/compose/Server/x86_64/iso')]) + + image = ImageCls.return_value + self.assertEqual(image.path, 'Server/x86_64/iso/image-name') + self.assertEqual(image.mtime, 13579) + self.assertEqual(image.size, 1024) + self.assertEqual(image.arch, 'x86_64') + self.assertEqual(image.type, "boot") + self.assertEqual(image.format, "iso") + self.assertEqual(image.disc_number, 1) + self.assertEqual(image.disc_count, 1) + self.assertEqual(image.bootable, True) + self.assertEqual(image.implant_md5, IsoWrapper.get_implanted_md5.return_value) + self.assertEqual(self.compose.im.add.mock_calls, + [mock.call('Server', 'x86_64', image)]) + + if __name__ == "__main__": unittest.main()