Fall back to mount if guestmount is not available
Relates: https://pagure.io/pungi/issue/803 Signed-off-by: Ondrej Nosek <onosek@redhat.com>
This commit is contained in:
parent
116e7ca3bd
commit
2152e7ea26
@ -42,7 +42,6 @@ Requires: gettext
|
|||||||
Requires: syslinux
|
Requires: syslinux
|
||||||
Requires: git
|
Requires: git
|
||||||
Requires: python-jsonschema
|
Requires: python-jsonschema
|
||||||
Requires: libguestfs-tools-c
|
|
||||||
Requires: python-enum34
|
Requires: python-enum34
|
||||||
Requires: python2-dnf
|
Requires: python2-dnf
|
||||||
Requires: python2-multilib
|
Requires: python2-multilib
|
||||||
|
@ -131,6 +131,9 @@ def create_product_img(compose, arch, variant):
|
|||||||
|
|
||||||
shutil.rmtree(po_tmp)
|
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_")
|
mount_tmp = compose.mkdtemp(prefix="product_img_mount_")
|
||||||
cmds = [
|
cmds = [
|
||||||
# allocate image
|
# allocate image
|
||||||
@ -139,7 +142,8 @@ def create_product_img(compose, arch, variant):
|
|||||||
"mke2fs -F %s" % shlex_quote(image),
|
"mke2fs -F %s" % shlex_quote(image),
|
||||||
# use guestmount to mount the image, which doesn't require root privileges
|
# use guestmount to mount the image, which doesn't require root privileges
|
||||||
# LIBGUESTFS_BACKEND=direct: running qemu directly without libvirt
|
# 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),
|
"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)),
|
"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),
|
"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),
|
"ln -s run/install/product/locale %s" % shlex_quote(mount_tmp),
|
||||||
# compat symlink: run/install/product/pyanaconda/installclasses -> ../installclasses
|
# compat symlink: run/install/product/pyanaconda/installclasses -> ../installclasses
|
||||||
"ln -s ../installclasses %s/run/install/product/pyanaconda/installclasses" % shlex_quote(mount_tmp),
|
"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
|
# tweak last mount path written in the image
|
||||||
"tune2fs -M /run/install/product %s" % shlex_quote(image),
|
"tune2fs -M /run/install/product %s" % shlex_quote(image),
|
||||||
]
|
]
|
||||||
|
@ -36,6 +36,7 @@ from productmd.common import get_major_version
|
|||||||
# Patterns that match all names of debuginfo packages
|
# Patterns that match all names of debuginfo packages
|
||||||
DEBUG_PATTERNS = ["*-debuginfo", "*-debuginfo-*", "*-debugsource"]
|
DEBUG_PATTERNS = ["*-debuginfo", "*-debuginfo-*", "*-debugsource"]
|
||||||
|
|
||||||
|
|
||||||
def _doRunCommand(command, logger, rundir='/tmp', output=subprocess.PIPE, error=subprocess.PIPE, env=None):
|
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"""
|
"""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)
|
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):
|
def run_unmount_cmd(cmd, max_retries=10, path=None, logger=None):
|
||||||
"""Attempt to run the command to unmount an image.
|
"""Attempt to run the command to unmount an image.
|
||||||
|
|
||||||
|
@ -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 yielded path will only be valid in the with block and is removed once
|
||||||
the image is unmounted.
|
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:
|
with util.temp_dir(prefix='iso-mount-') as mount_dir:
|
||||||
env = {'LIBGUESTFS_BACKEND': 'direct', 'LIBGUESTFS_DEBUG': '1', 'LIBGUESTFS_TRACE': '1'}
|
ret, __ = run(["which", "guestmount"], can_fail=True)
|
||||||
cmd = ["guestmount", "-a", image, "-m", "/dev/sda", mount_dir]
|
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)
|
ret, out = run(cmd, env=env, can_fail=True, universal_newlines=True)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
# The mount command failed, something is wrong. Log the output and raise an exception.
|
# The mount command failed, something is wrong. Log the output and raise an exception.
|
||||||
@ -400,4 +406,7 @@ def mount(image, logger=None):
|
|||||||
try:
|
try:
|
||||||
yield mount_dir
|
yield mount_dir
|
||||||
finally:
|
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)
|
||||||
|
@ -20,6 +20,7 @@ INCORRECT_OUTPUT = '''This should never happen: File not found'''
|
|||||||
|
|
||||||
|
|
||||||
class TestIsoUtils(unittest.TestCase):
|
class TestIsoUtils(unittest.TestCase):
|
||||||
|
|
||||||
@mock.patch('pungi.wrappers.iso.run')
|
@mock.patch('pungi.wrappers.iso.run')
|
||||||
def test_get_implanted_md5_correct(self, mock_run):
|
def test_get_implanted_md5_correct(self, mock_run):
|
||||||
mock_run.return_value = (0, CORRECT_OUTPUT)
|
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.util.run_unmount_cmd')
|
||||||
@mock.patch('pungi.wrappers.iso.run')
|
@mock.patch('pungi.wrappers.iso.run')
|
||||||
def test_mount_iso(self, mock_run, mock_unmount):
|
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:
|
with iso.mount('dummy') as temp_dir:
|
||||||
self.assertTrue(os.path.isdir(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(['mount'"))
|
||||||
self.assertEqual(len(mock_unmount.call_args_list), 1)
|
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), 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))
|
self.assertFalse(os.path.isdir(temp_dir))
|
||||||
|
|
||||||
@mock.patch('pungi.util.run_unmount_cmd')
|
@mock.patch('pungi.util.run_unmount_cmd')
|
||||||
@mock.patch('pungi.wrappers.iso.run')
|
@mock.patch('pungi.wrappers.iso.run')
|
||||||
def test_mount_iso_always_unmounts(self, mock_run, mock_unmount):
|
def test_mount_iso_always_unmounts(self, mock_run, mock_unmount):
|
||||||
mock_run.return_value = (0, '')
|
mock_run.side_effect = [(1, ''), (0, '')]
|
||||||
try:
|
try:
|
||||||
with iso.mount('dummy') as temp_dir:
|
with iso.mount('dummy') as temp_dir:
|
||||||
self.assertTrue(os.path.isdir(temp_dir))
|
self.assertTrue(os.path.isdir(temp_dir))
|
||||||
raise RuntimeError()
|
raise RuntimeError()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass
|
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.assertEqual(len(mock_unmount.call_args_list), 1)
|
||||||
self.assertFalse(os.path.isdir(temp_dir))
|
self.assertFalse(os.path.isdir(temp_dir))
|
||||||
|
|
||||||
@ -69,11 +94,11 @@ class TestIsoUtils(unittest.TestCase):
|
|||||||
@mock.patch('pungi.wrappers.iso.run')
|
@mock.patch('pungi.wrappers.iso.run')
|
||||||
def test_mount_iso_raises_on_error(self, mock_run, mock_unmount):
|
def test_mount_iso_raises_on_error(self, mock_run, mock_unmount):
|
||||||
log = mock.Mock()
|
log = mock.Mock()
|
||||||
mock_run.return_value = (1, 'Boom')
|
mock_run.side_effect = [(1, ''), (1, 'Boom')]
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
with iso.mount('dummy', logger=log) as temp_dir:
|
with iso.mount('dummy', logger=log) as temp_dir:
|
||||||
self.assertTrue(os.path.isdir(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(mock_unmount.call_args_list), 0)
|
||||||
self.assertEqual(len(log.mock_calls), 1)
|
self.assertEqual(len(log.mock_calls), 1)
|
||||||
|
|
||||||
|
@ -446,7 +446,7 @@ class TestUnmountCmd(unittest.TestCase):
|
|||||||
self._fakeProc(0, out='It is very busy'),
|
self._fakeProc(0, out='It is very busy'),
|
||||||
self._fakeProc(1, out='lsof output')]
|
self._fakeProc(1, out='lsof output')]
|
||||||
with self.assertRaises(RuntimeError) as ctx:
|
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']
|
cmd = ['fusermount', '-u', '/path']
|
||||||
expected = [mock.call(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
|
expected = [mock.call(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||||
universal_newlines=True),
|
universal_newlines=True),
|
||||||
|
Loading…
Reference in New Issue
Block a user