kiwi-el8/test/unit/mount_manager_test.py
Marcus Schäfer a7b984115d
Fixed delta_root build
The support for delta_root allows to build a delta container
image from a given base container. Due to the refactoring of
the kiwi code base using context managers no explicit deletion
of instances happens anymore. This uncovered a weakness of
the delta root code at the level of the overlay mount. At
the time of the umount there are still active temporary
mount handlers which keeps the mountpoint busy. In order to
fix this properly also the PackageManager factory is now
a context manager and the Repository factory received a
cleanup method which is called when the PackageManager goes
out of scope. This refactoring also fixes the busy state
when building deltas
2024-03-07 15:50:58 +01:00

219 lines
8.1 KiB
Python

import logging
from typing import NoReturn
from pytest import (
fixture, raises
)
from unittest.mock import (
patch, call, Mock
)
from kiwi.exceptions import KiwiCommandError, KiwiUmountBusyError
from kiwi.mount_manager import MountManager
class TestMountManager:
@fixture(autouse=True)
def inject_fixtures(self, caplog):
self._caplog = caplog
@patch('kiwi.mount_manager.Path.create')
def setup(self, mock_path_create):
self.mount_manager = MountManager(
'/dev/some-device', '/some/mountpoint'
)
mock_path_create.assert_called_once_with('/some/mountpoint')
@patch('kiwi.mount_manager.Path.create')
def setup_method(self, cls, mock_path_create):
self.setup()
def test_get_attributes(self):
assert self.mount_manager.get_attributes() == {}
@patch('kiwi.mount_manager.Temporary')
def test_setup_empty_mountpoint(self, mock_Temporary):
mock_Temporary.return_value.new_dir.return_value.name = 'tmpdir'
mount_manager = MountManager('/dev/some-device')
assert mount_manager.mountpoint == 'tmpdir'
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
def test_bind_mount(self, mock_mounted, mock_command):
mock_mounted.return_value = False
self.mount_manager.bind_mount()
mock_command.assert_called_once_with(
['mount', '-n', '--bind', '/dev/some-device', '/some/mountpoint']
)
@patch('kiwi.mount_manager.Path.create')
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
def test_overlay_mount(self, mock_mounted, mock_command, mock_path_create):
mock_mounted.return_value = False
self.mount_manager.overlay_mount('lower_path')
assert mock_path_create.call_args_list == [
call('/some/mountpoint_cow'), call('/some/mountpoint_work')
]
mock_command.assert_called_once_with(
[
'mount', '-t', 'overlay', 'overlay', '/some/mountpoint',
'-o', 'lowerdir=lower_path,'
'upperdir=/some/mountpoint_cow,workdir=/some/mountpoint_work'
]
)
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
def test_tmpfs_mount(self, mock_mounted, mock_command):
mock_mounted.return_value = False
self.mount_manager.tmpfs_mount()
mock_command.assert_called_once_with(
['mount', '-t', 'tmpfs', 'tmpfs', '/some/mountpoint']
)
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
def test_mount(self, mock_mounted, mock_command):
mock_mounted.return_value = False
self.mount_manager.mount(['options'])
mock_command.assert_called_once_with(
['mount', '-o', 'options', '/dev/some-device', '/some/mountpoint']
)
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
def test_context_manager(self, mock_mounted, mock_command):
mock_mounted.return_value = True
with self.mount_manager:
pass
mock_command.assert_called_once_with(
['umount', '/some/mountpoint']
)
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
def test_umount_lazy(self, mock_mounted, mock_command):
mock_mounted.return_value = True
self.mount_manager.umount_lazy()
mock_command.assert_called_once_with(
['umount', '-l', '/some/mountpoint']
)
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
@patch('time.sleep')
def test_umount_with_errors_but_lazy(
self, mock_sleep, mock_mounted, mock_command
):
command_errors = [
True, False, False, False, False, False
]
def _cmd_err(args) -> NoReturn:
if not command_errors.pop():
raise KiwiCommandError('error')
mock_command.side_effect = _cmd_err
mock_mounted.return_value = True
with self._caplog.at_level(logging.WARNING):
assert self.mount_manager.umount() is True
assert mock_command.call_args_list == [
call(['umount', '/some/mountpoint']), # 1
call(['umount', '/some/mountpoint']), # 2
call(['umount', '/some/mountpoint']), # 3
call(['umount', '/some/mountpoint']), # 4
call(['umount', '/some/mountpoint']), # 5
call(['umount', '--lazy', '/some/mountpoint']), # lazy
]
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
@patch('time.sleep')
def test_umount_with_errors(
self, mock_sleep, mock_mounted, mock_command
):
def _cmd_err(args) -> NoReturn:
raise KiwiCommandError('error')
mock_command.side_effect = _cmd_err
mock_mounted.return_value = True
with self._caplog.at_level(logging.WARNING):
assert self.mount_manager.umount(raise_on_busy=False) is False
assert mock_command.call_args_list == [
call(['umount', '/some/mountpoint']), # 1
call(['umount', '/some/mountpoint']), # 2
call(['umount', '/some/mountpoint']), # 3
call(['umount', '/some/mountpoint']), # 4
call(['umount', '/some/mountpoint']), # 5
call(['umount', '--lazy', '/some/mountpoint']), # lazy
]
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
@patch('time.sleep')
@patch('kiwi.mount_manager.Path.which')
def test_umount_with_errors_raises_no_lsof_present(
self, mock_Path_which, mock_sleep, mock_mounted, mock_command
):
def command_call(args):
if 'umount' in args:
raise KiwiCommandError('error')
mock_Path_which.return_value = None
mock_command.side_effect = command_call
mock_mounted.return_value = True
with raises(KiwiUmountBusyError):
self.mount_manager.umount()
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
@patch('time.sleep')
@patch('kiwi.mount_manager.Path.which')
def test_umount_with_errors_raises_lsof_present(
self, mock_Path_which, mock_sleep, mock_mounted, mock_command
):
def command_call(args, raise_on_error=None):
if 'umount' in args:
raise KiwiCommandError('error')
else:
call_return = Mock()
call_return.output = 'HEADLINE\ndata'
return call_return
mock_Path_which.return_value = 'lsof'
mock_command.side_effect = command_call
mock_mounted.return_value = True
with raises(KiwiUmountBusyError) as issue:
self.mount_manager.umount()
assert 'HEADLINE' in issue.value.message
@patch('kiwi.mount_manager.Command.run')
@patch('kiwi.mount_manager.MountManager.is_mounted')
def test_umount_success(self, mock_mounted, mock_command):
mock_mounted.return_value = True
assert self.mount_manager.umount() is True
mock_command.assert_called_once_with(
['umount', '/some/mountpoint']
)
@patch('kiwi.mount_manager.Command.run')
def test_is_mounted_true(self, mock_command):
command = Mock()
command.returncode = 0
mock_command.return_value = command
assert self.mount_manager.is_mounted() is True
mock_command.assert_called_once_with(
command=['mountpoint', '-q', '/some/mountpoint'],
raise_on_error=False
)
@patch('kiwi.mount_manager.Command.run')
def test_is_mounted_false(self, mock_command):
command = Mock()
command.returncode = 1
mock_command.return_value = command
assert self.mount_manager.is_mounted() is False
mock_command.assert_called_once_with(
command=['mountpoint', '-q', '/some/mountpoint'],
raise_on_error=False
)