pungi/0001-Fall-back-to-mount-if-guestmount-is-not-available.patch

218 lines
10 KiB
Diff
Raw Normal View History

From a07674254acb562332579a42a25c44c1a0e0bac6 Mon Sep 17 00:00:00 2001
From: Ondrej Nosek <onosek@redhat.com>
Date: Thu, 21 Dec 2017 03:18:00 +0100
Subject: [PATCH 1/2] Fall back to mount if guestmount is not available
Relates: https://pagure.io/pungi/issue/803
Signed-off-by: Ondrej Nosek <onosek@redhat.com>
---
pungi.spec | 1 -
pungi/phases/product_img.py | 9 +++++++--
pungi/util.py | 6 +-----
pungi/wrappers/iso.py | 19 ++++++++++++++-----
tests/test_iso_wrapper.py | 37 +++++++++++++++++++++++++++++++------
tests/test_util.py | 2 +-
6 files changed, 54 insertions(+), 20 deletions(-)
diff --git a/pungi.spec b/pungi.spec
index 545e409..82d305f 100644
--- a/pungi.spec
+++ b/pungi.spec
@@ -42,7 +42,6 @@ Requires: gettext
Requires: syslinux
Requires: git
Requires: python-jsonschema
-Requires: libguestfs-tools-c
Requires: python-enum34
Requires: python2-dnf
Requires: python2-multilib
diff --git a/pungi/phases/product_img.py b/pungi/phases/product_img.py
index 9a21eed..c956674 100644
--- a/pungi/phases/product_img.py
+++ b/pungi/phases/product_img.py
@@ -131,6 +131,9 @@ def create_product_img(compose, arch, variant):
shutil.rmtree(po_tmp)
+ ret, __ = run(["which", "guestmount"], can_fail=True)
+ guestmount_available = not bool(ret) # return code 0 means that guestmount is available
+
mount_tmp = compose.mkdtemp(prefix="product_img_mount_")
cmds = [
# allocate image
@@ -139,7 +142,8 @@ def create_product_img(compose, arch, variant):
"mke2fs -F %s" % shlex_quote(image),
# use guestmount to mount the image, which doesn't require root privileges
# LIBGUESTFS_BACKEND=direct: running qemu directly without libvirt
- "LIBGUESTFS_BACKEND=direct guestmount -a %s -m /dev/sda %s" % (shlex_quote(image), shlex_quote(mount_tmp)),
+ "LIBGUESTFS_BACKEND=direct guestmount -a %s -m /dev/sda %s" % (shlex_quote(image), shlex_quote(mount_tmp)) if guestmount_available
+ else "mount -o loop %s %s" % (shlex_quote(image), shlex_quote(mount_tmp)),
"mkdir -p %s/run/install/product" % shlex_quote(mount_tmp),
"cp -rp %s/* %s/run/install/product/" % (shlex_quote(product_tmp), shlex_quote(mount_tmp)),
"mkdir -p %s/run/install/product/pyanaconda" % shlex_quote(mount_tmp),
@@ -149,7 +153,8 @@ def create_product_img(compose, arch, variant):
"ln -s run/install/product/locale %s" % shlex_quote(mount_tmp),
# compat symlink: run/install/product/pyanaconda/installclasses -> ../installclasses
"ln -s ../installclasses %s/run/install/product/pyanaconda/installclasses" % shlex_quote(mount_tmp),
- "fusermount -u %s" % shlex_quote(mount_tmp),
+ "fusermount -u %s" % shlex_quote(mount_tmp) if guestmount_available
+ else "umount %s" % shlex_quote(mount_tmp),
# tweak last mount path written in the image
"tune2fs -M /run/install/product %s" % shlex_quote(image),
]
diff --git a/pungi/util.py b/pungi/util.py
index 2d2df82..b74f280 100644
--- a/pungi/util.py
+++ b/pungi/util.py
@@ -36,6 +36,7 @@ from productmd.common import get_major_version
# Patterns that match all names of debuginfo packages
DEBUG_PATTERNS = ["*-debuginfo", "*-debuginfo-*", "*-debugsource"]
+
def _doRunCommand(command, logger, rundir='/tmp', output=subprocess.PIPE, error=subprocess.PIPE, env=None):
"""Run a command and log the output. Error out if we get something on stderr"""
@@ -609,11 +610,6 @@ def temp_dir(log=None, *args, **kwargs):
log.warning('Error removing %s: %s', dir, exc.strerror)
-def fusermount(path, **kwargs):
- """Run fusermount -u on a given path."""
- run_unmount_cmd(['fusermount', '-u', path], path=path, **kwargs)
-
-
def run_unmount_cmd(cmd, max_retries=10, path=None, logger=None):
"""Attempt to run the command to unmount an image.
diff --git a/pungi/wrappers/iso.py b/pungi/wrappers/iso.py
index 7e2c710..9ed3f24 100644
--- a/pungi/wrappers/iso.py
+++ b/pungi/wrappers/iso.py
@@ -385,11 +385,17 @@ def mount(image, logger=None):
The yielded path will only be valid in the with block and is removed once
the image is unmounted.
"""
- # use guestmount to mount the image, which doesn't require root privileges
- # LIBGUESTFS_BACKEND=direct: running qemu directly without libvirt
with util.temp_dir(prefix='iso-mount-') as mount_dir:
- env = {'LIBGUESTFS_BACKEND': 'direct', 'LIBGUESTFS_DEBUG': '1', 'LIBGUESTFS_TRACE': '1'}
- cmd = ["guestmount", "-a", image, "-m", "/dev/sda", mount_dir]
+ ret, __ = run(["which", "guestmount"], can_fail=True)
+ guestmount_available = not bool(ret) # return code 0 means that guestmount is available
+ if guestmount_available:
+ # use guestmount to mount the image, which doesn't require root privileges
+ # LIBGUESTFS_BACKEND=direct: running qemu directly without libvirt
+ env = {'LIBGUESTFS_BACKEND': 'direct', 'LIBGUESTFS_DEBUG': '1', 'LIBGUESTFS_TRACE': '1'}
+ cmd = ["guestmount", "-a", image, "-m", "/dev/sda", mount_dir]
+ else:
+ env = {}
+ cmd = ["mount", "-o", "loop", image, mount_dir]
ret, out = run(cmd, env=env, can_fail=True, universal_newlines=True)
if ret != 0:
# The mount command failed, something is wrong. Log the output and raise an exception.
@@ -400,4 +406,7 @@ def mount(image, logger=None):
try:
yield mount_dir
finally:
- util.fusermount(mount_dir, logger=logger)
+ if guestmount_available:
+ util.run_unmount_cmd(['fusermount', '-u', mount_dir], path=mount_dir)
+ else:
+ util.run_unmount_cmd(['umount', mount_dir], path=mount_dir)
diff --git a/tests/test_iso_wrapper.py b/tests/test_iso_wrapper.py
index a4a00a1..7ebc2c3 100644
--- a/tests/test_iso_wrapper.py
+++ b/tests/test_iso_wrapper.py
@@ -20,6 +20,7 @@ INCORRECT_OUTPUT = '''This should never happen: File not found'''
class TestIsoUtils(unittest.TestCase):
+
@mock.patch('pungi.wrappers.iso.run')
def test_get_implanted_md5_correct(self, mock_run):
mock_run.return_value = (0, CORRECT_OUTPUT)
@@ -44,24 +45,48 @@ class TestIsoUtils(unittest.TestCase):
@mock.patch('pungi.util.run_unmount_cmd')
@mock.patch('pungi.wrappers.iso.run')
def test_mount_iso(self, mock_run, mock_unmount):
- mock_run.return_value = (0, '')
+ # first tuple is return value for command 'which guestmount'
+ # value determines type of the mount/unmount command ('1' - guestmount is not available)
+ # for approach as a root, pair commands mount-umount are used
+ mock_run.side_effect = [(1, ''), (0, '')]
+ with iso.mount('dummy') as temp_dir:
+ self.assertTrue(os.path.isdir(temp_dir))
+ self.assertEqual(len(mock_run.call_args_list), 2)
+ mount_call_str = str(mock_run.call_args_list[1])
+ self.assertTrue(mount_call_str.startswith("call(['mount'"))
+ self.assertEqual(len(mock_unmount.call_args_list), 1)
+ unmount_call_str = str(mock_unmount.call_args_list[0])
+ self.assertTrue(unmount_call_str.startswith("call(['umount'"))
+ self.assertFalse(os.path.isdir(temp_dir))
+
+ @mock.patch('pungi.util.run_unmount_cmd')
+ @mock.patch('pungi.wrappers.iso.run')
+ def test_guestmount(self, mock_run, mock_unmount):
+ # first tuple is return value for command 'which guestmount'
+ # value determines type of the mount/unmount command ('0' - guestmount is available)
+ # for approach as a non-root, pair commands guestmount-fusermount are used
+ mock_run.side_effect = [(0, ''), (0, '')]
with iso.mount('dummy') as temp_dir:
self.assertTrue(os.path.isdir(temp_dir))
- self.assertEqual(len(mock_run.call_args_list), 1)
+ self.assertEqual(len(mock_run.call_args_list), 2)
+ mount_call_str = str(mock_run.call_args_list[1])
+ self.assertTrue(mount_call_str.startswith("call(['guestmount'"))
self.assertEqual(len(mock_unmount.call_args_list), 1)
+ unmount_call_str = str(mock_unmount.call_args_list[0])
+ self.assertTrue(unmount_call_str.startswith("call(['fusermount'"))
self.assertFalse(os.path.isdir(temp_dir))
@mock.patch('pungi.util.run_unmount_cmd')
@mock.patch('pungi.wrappers.iso.run')
def test_mount_iso_always_unmounts(self, mock_run, mock_unmount):
- mock_run.return_value = (0, '')
+ mock_run.side_effect = [(1, ''), (0, '')]
try:
with iso.mount('dummy') as temp_dir:
self.assertTrue(os.path.isdir(temp_dir))
raise RuntimeError()
except RuntimeError:
pass
- self.assertEqual(len(mock_run.call_args_list), 1)
+ self.assertEqual(len(mock_run.call_args_list), 2)
self.assertEqual(len(mock_unmount.call_args_list), 1)
self.assertFalse(os.path.isdir(temp_dir))
@@ -69,11 +94,11 @@ class TestIsoUtils(unittest.TestCase):
@mock.patch('pungi.wrappers.iso.run')
def test_mount_iso_raises_on_error(self, mock_run, mock_unmount):
log = mock.Mock()
- mock_run.return_value = (1, 'Boom')
+ mock_run.side_effect = [(1, ''), (1, 'Boom')]
with self.assertRaises(RuntimeError):
with iso.mount('dummy', logger=log) as temp_dir:
self.assertTrue(os.path.isdir(temp_dir))
- self.assertEqual(len(mock_run.call_args_list), 1)
+ self.assertEqual(len(mock_run.call_args_list), 2)
self.assertEqual(len(mock_unmount.call_args_list), 0)
self.assertEqual(len(log.mock_calls), 1)
diff --git a/tests/test_util.py b/tests/test_util.py
index e0dd2b1..7529a25 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -446,7 +446,7 @@ class TestUnmountCmd(unittest.TestCase):
self._fakeProc(0, out='It is very busy'),
self._fakeProc(1, out='lsof output')]
with self.assertRaises(RuntimeError) as ctx:
- util.fusermount('/path', max_retries=3, logger=logger)
+ util.run_unmount_cmd(['fusermount', '-u', '/path'], path='/path', max_retries=3, logger=logger)
cmd = ['fusermount', '-u', '/path']
expected = [mock.call(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
universal_newlines=True),
--
2.13.6