Example #1
0
 def _get_rpm_database_location(self):
     shared_mount = MountManager(device='/dev',
                                 mountpoint=self.root_dir + '/dev')
     if not shared_mount.is_mounted():
         shared_mount.bind_mount()
     rpmdb = RpmDataBase(self.root_dir)
     if rpmdb.has_rpm():
         dbpath = rpmdb.rpmdb_image.expand_query('%_dbpath')
     else:
         dbpath = rpmdb.rpmdb_host.expand_query('%_dbpath')
     if shared_mount.is_mounted():
         shared_mount.umount_lazy()
     return dbpath
Example #2
0
    def sync_data(self, exclude=None):
        """
        Implements sync of root directory to mounted volumes

        :param list exclude: file patterns to exclude
        """
        if self.mountpoint:
            root_mount = MountManager(device=None, mountpoint=self.mountpoint)
            if not root_mount.is_mounted():
                self.mount_volumes()
            data = DataSync(self.root_dir, self.mountpoint)
            data.sync_data(options=Defaults.get_sync_options(),
                           exclude=exclude)
Example #3
0
File: base.py Project: isbm/kiwi
    def sync_data(self, exclude=None):
        """
        Implements sync of root directory to mounted volumes

        :param list exclude: file patterns to exclude
        """
        if self.mountpoint:
            root_mount = MountManager(device=None, mountpoint=self.mountpoint)
            if not root_mount.is_mounted():
                self.mount_volumes()
            data = DataSync(self.root_dir, self.mountpoint)
            data.sync_data(
                options=['-a', '-H', '-X', '-A', '--one-file-system'],
                exclude=exclude)
Example #4
0
    def sync_data(self, exclude=None):
        """
        Implements sync of root directory to mounted volumes

        :param list exclude: file patterns to exclude
        """
        if self.mountpoint:
            root_mount = MountManager(device=None, mountpoint=self.mountpoint)
            if not root_mount.is_mounted():
                self.mount_volumes()
            data = DataSync(self.root_dir, self.mountpoint)
            data.sync_data(
                options=['-a', '-H', '-X', '-A', '--one-file-system'],
                exclude=exclude
            )
            self.umount_volumes()
Example #5
0
class VolumeManagerBtrfs(VolumeManagerBase):
    """
    Implements btrfs sub-volume management

    :param list subvol_mount_list: list of mounted btrfs subvolumes
    :param object toplevel_mount: :class:`MountManager` for root mountpoint
    """
    def post_init(self, custom_args):
        """
        Post initialization method

        Store custom btrfs initialization arguments

        :param list custom_args: custom btrfs volume manager arguments
        """
        if custom_args:
            self.custom_args = custom_args
        else:
            self.custom_args = {}
        if 'root_label' not in self.custom_args:
            self.custom_args['root_label'] = 'ROOT'
        if 'root_is_snapshot' not in self.custom_args:
            self.custom_args['root_is_snapshot'] = False
        if 'root_is_readonly_snapshot' not in self.custom_args:
            self.custom_args['root_is_readonly_snapshot'] = False
        if 'quota_groups' not in self.custom_args:
            self.custom_args['quota_groups'] = False

        self.subvol_mount_list = []
        self.toplevel_mount = None
        self.toplevel_volume = None

    def setup(self, name=None):
        """
        Setup btrfs volume management

        In case of btrfs a toplevel(@) subvolume is created and marked
        as default volume. If snapshots are activated via the custom_args
        the setup method also created the @/.snapshots/1/snapshot
        subvolumes. There is no concept of a volume manager name, thus
        the name argument is not used for btrfs

        :param string name: unused
        """
        self.setup_mountpoint()

        filesystem = FileSystem.new(
            name='btrfs',
            device_provider=MappedDevice(
                device=self.device, device_provider=self.device_provider_root),
            custom_args=self.custom_filesystem_args)
        filesystem.create_on_device(label=self.custom_args['root_label'])
        self.toplevel_mount = MountManager(device=self.device,
                                           mountpoint=self.mountpoint)
        self.toplevel_mount.mount(self.custom_filesystem_args['mount_options'])
        if self.custom_args['quota_groups']:
            Command.run(['btrfs', 'quota', 'enable', self.mountpoint])
        root_volume = self.mountpoint + '/@'
        Command.run(['btrfs', 'subvolume', 'create', root_volume])
        if self.custom_args['root_is_snapshot']:
            snapshot_volume = self.mountpoint + '/@/.snapshots'
            Command.run(['btrfs', 'subvolume', 'create', snapshot_volume])
            volume_mount = MountManager(device=self.device,
                                        mountpoint=self.mountpoint +
                                        '/.snapshots')
            self.subvol_mount_list.append(volume_mount)
            Path.create(snapshot_volume + '/1')
            snapshot = self.mountpoint + '/@/.snapshots/1/snapshot'
            Command.run(
                ['btrfs', 'subvolume', 'snapshot', root_volume, snapshot])
            self._set_default_volume('@/.snapshots/1/snapshot')
        else:
            self._set_default_volume('@')

    def create_volumes(self, filesystem_name):
        """
        Create configured btrfs subvolumes

        Any btrfs subvolume is of the same btrfs filesystem. There is no
        way to have different filesystems per btrfs subvolume. Thus
        the filesystem_name has no effect for btrfs

        :param string filesystem_name: unused
        """
        log.info('Creating %s sub volumes', filesystem_name)
        self.create_volume_paths_in_root_dir()

        canonical_volume_list = self.get_canonical_volume_list()
        if canonical_volume_list.full_size_volume:
            # put an eventual fullsize volume to the volume list
            # because there is no extra handling required for it on btrfs
            canonical_volume_list.volumes.append(
                canonical_volume_list.full_size_volume)

        for volume in canonical_volume_list.volumes:
            if volume.is_root_volume:
                # the btrfs root volume named '@' has been created as
                # part of the setup procedure
                pass
            else:
                log.info('--> sub volume %s', volume.realpath)
                toplevel = self.mountpoint + '/@/'
                volume_parent_path = os.path.normpath(
                    toplevel + os.path.dirname(volume.realpath))
                if not os.path.exists(volume_parent_path):
                    Path.create(volume_parent_path)
                Command.run([
                    'btrfs', 'subvolume', 'create',
                    os.path.normpath(toplevel + volume.realpath)
                ])
                self.apply_attributes_on_volume(toplevel, volume)
                if self.custom_args['root_is_snapshot']:
                    snapshot = self.mountpoint + '/@/.snapshots/1/snapshot/'
                    volume_mount = MountManager(
                        device=self.device,
                        mountpoint=os.path.normpath(snapshot +
                                                    volume.realpath))
                    self.subvol_mount_list.append(volume_mount)

    def get_fstab(self, persistency_type='by-label', filesystem_name=None):
        """
        Implements creation of the fstab entries. The method
        returns a list of fstab compatible entries

        :param string persistency_type: by-label | by-uuid
        :param string filesystem_name: unused

        :return: list of fstab entries

        :rtype: list
        """
        fstab_entries = []
        mount_options = \
            self.custom_filesystem_args['mount_options'] or ['defaults']
        block_operation = BlockID(self.device)
        blkid_type = 'LABEL' if persistency_type == 'by-label' else 'UUID'
        device_id = block_operation.get_blkid(blkid_type)
        for volume_mount in self.subvol_mount_list:
            subvol_name = self._get_subvol_name_from_mountpoint(volume_mount)
            mount_entry_options = mount_options + ['subvol=' + subvol_name]
            fs_check = self._is_volume_enabled_for_fs_check(
                volume_mount.mountpoint)
            fstab_entry = ' '.join([
                blkid_type + '=' + device_id,
                subvol_name.replace('@', ''), 'btrfs',
                ','.join(mount_entry_options),
                '0 {fs_passno}'.format(fs_passno='2' if fs_check else '0')
            ])
            fstab_entries.append(fstab_entry)
        return fstab_entries

    def get_volumes(self):
        """
        Return dict of volumes

        :return: volumes dictionary

        :rtype: dict
        """
        volumes = {}
        for volume_mount in self.subvol_mount_list:
            subvol_name = self._get_subvol_name_from_mountpoint(volume_mount)
            subvol_options = ','.join(
                ['subvol=' + subvol_name] +
                self.custom_filesystem_args['mount_options'])
            volumes[subvol_name.replace('@', '')] = {
                'volume_options': subvol_options,
                'volume_device': volume_mount.device
            }
        return volumes

    def mount_volumes(self):
        """
        Mount btrfs subvolumes
        """
        self.toplevel_mount.mount(self.custom_filesystem_args['mount_options'])

        for volume_mount in self.subvol_mount_list:
            if self.volumes_mounted_initially:
                volume_mount.mountpoint = os.path.normpath(
                    volume_mount.mountpoint.replace(self.toplevel_volume, '',
                                                    1))
            if not os.path.exists(volume_mount.mountpoint):
                Path.create(volume_mount.mountpoint)
            subvol_name = self._get_subvol_name_from_mountpoint(volume_mount)
            subvol_options = ','.join(
                ['subvol=' + subvol_name] +
                self.custom_filesystem_args['mount_options'])
            volume_mount.mount(options=[subvol_options])

        self.volumes_mounted_initially = True

    def umount_volumes(self):
        """
        Umount btrfs subvolumes

        :return: True if all subvolumes are successfully unmounted

        :rtype: bool
        """
        all_volumes_umounted = True
        for volume_mount in reversed(self.subvol_mount_list):
            if volume_mount.is_mounted():
                if not volume_mount.umount():
                    all_volumes_umounted = False

        if all_volumes_umounted:
            if self.toplevel_mount.is_mounted():
                if not self.toplevel_mount.umount():
                    all_volumes_umounted = False

        return all_volumes_umounted

    def get_mountpoint(self) -> str:
        """
        Provides btrfs root mount point directory

        Effective use of the directory is guaranteed only after sync_data

        :return: directory path name

        :rtype: string
        """
        sync_target: List[str] = [self.mountpoint, '@']
        if self.custom_args.get('root_is_snapshot'):
            sync_target.extend(['.snapshots', '1', 'snapshot'])
        return os.path.join(*sync_target)

    def sync_data(self, exclude=None):
        """
        Sync data into btrfs filesystem

        If snapshots are activated the root filesystem is synced
        into the first snapshot

        :param list exclude: files to exclude from sync
        """
        if self.toplevel_mount:
            sync_target = self.get_mountpoint()
            if self.custom_args['root_is_snapshot']:
                self._create_snapshot_info(''.join(
                    [self.mountpoint, '/@/.snapshots/1/info.xml']))
            data = DataSync(self.root_dir, sync_target)
            data.sync_data(options=Defaults.get_sync_options(),
                           exclude=exclude)
            if self.custom_args['quota_groups'] and \
               self.custom_args['root_is_snapshot']:
                self._create_snapper_quota_configuration()

    def set_property_readonly_root(self):
        """
        Sets the root volume to be a readonly filesystem
        """
        root_is_snapshot = \
            self.custom_args['root_is_snapshot']
        root_is_readonly_snapshot = \
            self.custom_args['root_is_readonly_snapshot']
        if root_is_snapshot and root_is_readonly_snapshot:
            sync_target = self.mountpoint
            Command.run(
                ['btrfs', 'property', 'set', sync_target, 'ro', 'true'])

    def _is_volume_enabled_for_fs_check(self, mountpoint):
        for volume in self.volumes:
            if volume.realpath in mountpoint:
                if 'enable-for-filesystem-check' in volume.attributes:
                    return True
        return False

    def _set_default_volume(self, default_volume):
        subvolume_list_call = Command.run(
            ['btrfs', 'subvolume', 'list', self.mountpoint])
        for subvolume in subvolume_list_call.output.split('\n'):
            id_search = re.search('ID (\d+) .*path (.*)', subvolume)
            if id_search:
                volume_id = id_search.group(1)
                volume_path = id_search.group(2)
                if volume_path == default_volume:
                    Command.run([
                        'btrfs', 'subvolume', 'set-default', volume_id,
                        self.mountpoint
                    ])
                    self.toplevel_volume = default_volume
                    return

        raise KiwiVolumeRootIDError('Failed to find btrfs volume: %s' %
                                    default_volume)

    def _xml_pretty(self, toplevel_element):
        xml_data_unformatted = ElementTree.tostring(toplevel_element, 'utf-8')
        xml_data_domtree = minidom.parseString(xml_data_unformatted)
        return xml_data_domtree.toprettyxml(indent="    ")

    def _create_snapper_quota_configuration(self):
        root_path = os.sep.join([self.mountpoint, '@/.snapshots/1/snapshot'])
        snapper_default_conf = os.path.normpath(
            os.sep.join(
                [root_path,
                 Defaults.get_snapper_config_template_file()]))
        if os.path.exists(snapper_default_conf):
            # snapper requires an extra parent qgroup to operate with quotas
            Command.run(['btrfs', 'qgroup', 'create', '1/0', self.mountpoint])
            config_file = self._set_snapper_sysconfig_file(root_path)
            if not os.path.exists(config_file):
                shutil.copyfile(snapper_default_conf, config_file)
            Command.run([
                'chroot', root_path, 'snapper', '--no-dbus', 'set-config',
                'QGROUP=1/0'
            ])

    @staticmethod
    def _set_snapper_sysconfig_file(root_path):
        sysconf_file = SysConfig(
            os.sep.join([root_path, 'etc/sysconfig/snapper']))
        if not sysconf_file.get('SNAPPER_CONFIGS') or \
           len(sysconf_file['SNAPPER_CONFIGS'].strip('\"')) == 0:

            sysconf_file['SNAPPER_CONFIGS'] = '"root"'
            sysconf_file.write()
        elif len(sysconf_file['SNAPPER_CONFIGS'].split()) > 1:
            raise KiwiVolumeManagerSetupError(
                'Unsupported SNAPPER_CONFIGS value: {0}'.format(
                    sysconf_file['SNAPPER_CONFIGS']))
        return os.sep.join([
            root_path, 'etc/snapper/configs',
            sysconf_file['SNAPPER_CONFIGS'].strip('\"')
        ])

    def _create_snapshot_info(self, filename):
        date_info = datetime.datetime.now()
        snapshot = ElementTree.Element('snapshot')

        snapshot_type = ElementTree.SubElement(snapshot, 'type')
        snapshot_type.text = 'single'

        snapshot_number = ElementTree.SubElement(snapshot, 'num')
        snapshot_number.text = '1'

        snapshot_description = ElementTree.SubElement(snapshot, 'description')
        snapshot_description.text = 'first root filesystem'

        snapshot_date = ElementTree.SubElement(snapshot, 'date')
        snapshot_date.text = date_info.strftime("%Y-%m-%d %H:%M:%S")

        with open(filename, 'w') as snapshot_info_file:
            snapshot_info_file.write(self._xml_pretty(snapshot))

    def _get_subvol_name_from_mountpoint(self, volume_mount):
        path_start_index = len(defaults.TEMP_DIR.split(os.sep)) + 1
        subvol_name = os.sep.join(
            volume_mount.mountpoint.split(os.sep)[path_start_index:])
        if self.toplevel_volume and self.toplevel_volume in subvol_name:
            subvol_name = subvol_name.replace(self.toplevel_volume, '')
        return os.path.normpath(os.sep.join(['@', subvol_name]))

    def __del__(self):
        if self.toplevel_mount:
            log.info('Cleaning up %s instance', type(self).__name__)
            if not self.umount_volumes():
                log.warning('Subvolumes still busy')
Example #6
0
class VolumeManagerBtrfs(VolumeManagerBase):
    """
    Implements btrfs sub-volume management

    :param list subvol_mount_list: list of mounted btrfs subvolumes
    :param object toplevel_mount: :class:`MountManager` for root mountpoint
    """
    def post_init(self, custom_args):
        """
        Post initialization method

        Store custom btrfs initialization arguments

        :param list custom_args: custom btrfs volume manager arguments
        """
        if custom_args:
            self.custom_args = custom_args
        else:
            self.custom_args = {}
        if 'root_label' not in self.custom_args:
            self.custom_args['root_label'] = 'ROOT'
        if 'root_is_snapshot' not in self.custom_args:
            self.custom_args['root_is_snapshot'] = False
        if 'root_is_readonly_snapshot' not in self.custom_args:
            self.custom_args['root_is_readonly_snapshot'] = False

        self.subvol_mount_list = []
        self.toplevel_mount = None
        self.toplevel_volume = None

    def setup(self, name=None):
        """
        Setup btrfs volume management

        In case of btrfs a toplevel(@) subvolume is created and marked
        as default volume. If snapshots are activated via the custom_args
        the setup method also created the @/.snapshots/1/snapshot
        subvolumes. There is no concept of a volume manager name, thus
        the name argument is not used for btrfs

        :param string name: unused
        """
        self.setup_mountpoint()

        filesystem = FileSystem(
            name='btrfs',
            device_provider=MappedDevice(
                device=self.device, device_provider=self
            ),
            custom_args=self.custom_filesystem_args
        )
        filesystem.create_on_device(
            label=self.custom_args['root_label']
        )
        self.toplevel_mount = MountManager(
            device=self.device, mountpoint=self.mountpoint
        )
        self.toplevel_mount.mount(
            self.custom_filesystem_args['mount_options']
        )
        root_volume = self.mountpoint + '/@'
        Command.run(
            ['btrfs', 'subvolume', 'create', root_volume]
        )
        if self.custom_args['root_is_snapshot']:
            snapshot_volume = self.mountpoint + '/@/.snapshots'
            Command.run(
                ['btrfs', 'subvolume', 'create', snapshot_volume]
            )
            Path.create(snapshot_volume + '/1')
            snapshot = self.mountpoint + '/@/.snapshots/1/snapshot'
            Command.run(
                ['btrfs', 'subvolume', 'snapshot', root_volume, snapshot]
            )
            self._set_default_volume('@/.snapshots/1/snapshot')
        else:
            self._set_default_volume('@')

    def create_volumes(self, filesystem_name):
        """
        Create configured btrfs subvolumes

        Any btrfs subvolume is of the same btrfs filesystem. There is no
        way to have different filesystems per btrfs subvolume. Thus
        the filesystem_name has no effect for btrfs

        :param string filesystem_name: unused
        """
        log.info(
            'Creating %s sub volumes', filesystem_name
        )
        self.create_volume_paths_in_root_dir()

        canonical_volume_list = self.get_canonical_volume_list()
        if canonical_volume_list.full_size_volume:
            # put an eventual fullsize volume to the volume list
            # because there is no extra handling required for it on btrfs
            canonical_volume_list.volumes.append(
                canonical_volume_list.full_size_volume
            )

        for volume in canonical_volume_list.volumes:
            if volume.name == 'LVRoot':
                # the btrfs root volume named '@' has been created as
                # part of the setup procedure
                pass
            else:
                log.info('--> sub volume %s', volume.realpath)
                toplevel = self.mountpoint + '/@/'
                volume_parent_path = os.path.normpath(
                    toplevel + os.path.dirname(volume.realpath)
                )
                if not os.path.exists(volume_parent_path):
                    Path.create(volume_parent_path)
                Command.run(
                    [
                        'btrfs', 'subvolume', 'create',
                        os.path.normpath(toplevel + volume.realpath)
                    ]
                )
                self.apply_attributes_on_volume(
                    toplevel, volume
                )
                if self.custom_args['root_is_snapshot']:
                    snapshot = self.mountpoint + '/@/.snapshots/1/snapshot/'
                    volume_mount = MountManager(
                        device=self.device,
                        mountpoint=os.path.normpath(snapshot + volume.realpath)
                    )
                    self.subvol_mount_list.append(
                        volume_mount
                    )

    def get_fstab(self, persistency_type='by-label', filesystem_name=None):
        """
        Implements creation of the fstab entries. The method
        returns a list of fstab compatible entries

        :param string persistency_type: by-label | by-uuid
        :param string filesystem_name: unused

        :return: list of fstab entries

        :rtype: list
        """
        fstab_entries = []
        mount_options = \
            self.custom_filesystem_args['mount_options'] or ['defaults']
        block_operation = BlockID(self.device)
        blkid_type = 'LABEL' if persistency_type == 'by-label' else 'UUID'
        device_id = block_operation.get_blkid(blkid_type)
        if self.custom_args['root_is_snapshot']:
            mount_entry_options = mount_options + ['subvol=@/.snapshots']
            fstab_entry = ' '.join(
                [
                    blkid_type + '=' + device_id, '/.snapshots',
                    'btrfs', ','.join(mount_entry_options), '0 0'
                ]
            )
            fstab_entries.append(fstab_entry)
        for volume_mount in self.subvol_mount_list:
            subvol_name = self._get_subvol_name_from_mountpoint(volume_mount)
            mount_entry_options = mount_options + ['subvol=' + subvol_name]
            fstab_entry = ' '.join(
                [
                    blkid_type + '=' + device_id, subvol_name.replace('@', ''),
                    'btrfs', ','.join(mount_entry_options), '0 0'
                ]
            )
            fstab_entries.append(fstab_entry)
        return fstab_entries

    def get_volumes(self):
        """
        Return dict of volumes

        :return: volumes dictionary

        :rtype: dict
        """
        volumes = {}
        for volume_mount in self.subvol_mount_list:
            subvol_name = self._get_subvol_name_from_mountpoint(volume_mount)
            subvol_options = ','.join(
                [
                    'subvol=' + subvol_name
                ] + self.custom_filesystem_args['mount_options']
            )
            volumes[subvol_name.replace('@', '')] = {
                'volume_options': subvol_options,
                'volume_device': volume_mount.device
            }
        return volumes

    def mount_volumes(self):
        """
        Mount btrfs subvolumes
        """

        self.toplevel_mount.mount(
            self.custom_filesystem_args['mount_options']
        )

        for volume_mount in self.subvol_mount_list:
            if self.volumes_mounted_initially:
                volume_mount.mountpoint = os.path.normpath(
                    volume_mount.mountpoint.replace(self.toplevel_volume, '', 1)
                )
            if not os.path.exists(volume_mount.mountpoint):
                Path.create(volume_mount.mountpoint)
            subvol_name = self._get_subvol_name_from_mountpoint(volume_mount)
            subvol_options = ','.join(
                [
                    'subvol=' + subvol_name
                ] + self.custom_filesystem_args['mount_options']
            )
            volume_mount.mount(
                options=[subvol_options]
            )

        self.volumes_mounted_initially = True

    def umount_volumes(self):
        """
        Umount btrfs subvolumes

        :return: True if all subvolumes are successfully unmounted

        :rtype: bool
        """
        all_volumes_umounted = True
        for volume_mount in reversed(self.subvol_mount_list):
            if volume_mount.is_mounted():
                if not volume_mount.umount():
                    all_volumes_umounted = False

        if all_volumes_umounted:
            if self.toplevel_mount.is_mounted():
                if not self.toplevel_mount.umount():
                    all_volumes_umounted = False

        return all_volumes_umounted

    def sync_data(self, exclude=None):
        """
        Sync data into btrfs filesystem

        If snapshots are activated the root filesystem is synced
        into the first snapshot

        :param list exclude: files to exclude from sync
        """
        if self.toplevel_mount:
            sync_target = self.mountpoint + '/@'
            if self.custom_args['root_is_snapshot']:
                sync_target = self.mountpoint + '/@/.snapshots/1/snapshot'
                self._create_snapshot_info(
                    ''.join([self.mountpoint, '/@/.snapshots/1/info.xml'])
                )
            data = DataSync(self.root_dir, sync_target)
            data.sync_data(
                options=['-a', '-H', '-X', '-A', '--one-file-system'],
                exclude=exclude
            )

    def set_property_readonly_root(self):
        """
        Sets the root volume to be a readonly filesystem
        """
        root_is_snapshot = \
            self.custom_args['root_is_snapshot']
        root_is_readonly_snapshot = \
            self.custom_args['root_is_readonly_snapshot']
        if root_is_snapshot and root_is_readonly_snapshot:
            sync_target = self.mountpoint
            Command.run(
                ['btrfs', 'property', 'set', sync_target, 'ro', 'true']
            )

    def _set_default_volume(self, default_volume):
        subvolume_list_call = Command.run(
            ['btrfs', 'subvolume', 'list', self.mountpoint]
        )
        for subvolume in subvolume_list_call.output.split('\n'):
            id_search = re.search('ID (\d+) .*path (.*)', subvolume)
            if id_search:
                volume_id = id_search.group(1)
                volume_path = id_search.group(2)
                if volume_path == default_volume:
                    Command.run(
                        [
                            'btrfs', 'subvolume', 'set-default',
                            volume_id, self.mountpoint
                        ]
                    )
                    self.toplevel_volume = default_volume
                    return

        raise KiwiVolumeRootIDError(
            'Failed to find btrfs volume: %s' % default_volume
        )

    def _xml_pretty(self, toplevel_element):
        xml_data_unformatted = ElementTree.tostring(
            toplevel_element, 'utf-8'
        )
        xml_data_domtree = minidom.parseString(xml_data_unformatted)
        return xml_data_domtree.toprettyxml(indent="    ")

    def _create_snapshot_info(self, filename):
        date_info = datetime.datetime.now()
        snapshot = ElementTree.Element('snapshot')

        snapshot_type = ElementTree.SubElement(snapshot, 'type')
        snapshot_type.text = 'single'

        snapshot_number = ElementTree.SubElement(snapshot, 'num')
        snapshot_number.text = '1'

        snapshot_description = ElementTree.SubElement(snapshot, 'description')
        snapshot_description.text = 'first root filesystem'

        snapshot_date = ElementTree.SubElement(snapshot, 'date')
        snapshot_date.text = date_info.strftime("%Y-%m-%d %H:%M:%S")

        with open(filename, 'w') as snapshot_info_file:
            snapshot_info_file.write(self._xml_pretty(snapshot))

    def _get_subvol_name_from_mountpoint(self, volume_mount):
        subvol_name = '/'.join(volume_mount.mountpoint.split('/')[3:])
        if self.toplevel_volume and self.toplevel_volume in subvol_name:
            subvol_name = subvol_name.replace(self.toplevel_volume, '')
        return os.path.normpath(os.sep.join(['@', subvol_name]))

    def __del__(self):
        if self.toplevel_mount:
            log.info('Cleaning up %s instance', type(self).__name__)
            if self.umount_volumes():
                Path.wipe(self.mountpoint)
Example #7
0
class TestMountManager:
    def setup(self):
        self.mount_manager = MountManager(
            '/dev/some-device', '/some/mountpoint'
        )

    @patch('kiwi.mount_manager.mkdtemp')
    def test_setup_empty_mountpoint(self, mock_mkdtemp):
        mock_mkdtemp.return_value = '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.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_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')
    @patch('kiwi.logger.log.warning')
    def test_umount_with_errors(
        self, mock_warn, mock_sleep, mock_mounted, mock_command
    ):
        mock_command.side_effect = Exception
        mock_mounted.return_value = True
        assert self.mount_manager.umount() is False
        assert mock_command.call_args_list == [
            call(['umount', '/some/mountpoint']),
            call(['umount', '/some/mountpoint']),
            call(['umount', '/some/mountpoint'])
        ]
        assert mock_warn.called

    @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.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.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
        )

    @patch('kiwi.mount_manager.Path.wipe')
    @patch('kiwi.mount_manager.MountManager.is_mounted')
    def test_destructor(self, mock_mounted, mock_wipe):
        self.mount_manager.mountpoint_created_by_mount_manager = True
        mock_mounted.return_value = False
        self.mount_manager.__del__()
        mock_wipe.assert_called_once_with('/some/mountpoint')
Example #8
0
class TestMountManager(object):
    def setup(self):
        self.mount_manager = MountManager(
            '/dev/some-device', '/some/mountpoint'
        )

    @patch('kiwi.mount_manager.mkdtemp')
    def test_setup_empty_mountpoint(self, mock_mkdtemp):
        mock_mkdtemp.return_value = '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.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_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')
    @patch('kiwi.logger.log.warning')
    def test_umount_with_errors(
        self, mock_warn, mock_sleep, mock_mounted, mock_command
    ):
        mock_command.side_effect = Exception
        mock_mounted.return_value = True
        assert self.mount_manager.umount() is False
        assert mock_command.call_args_list == [
            call(['umount', '/some/mountpoint']),
            call(['umount', '/some/mountpoint']),
            call(['umount', '/some/mountpoint'])
        ]
        assert mock_warn.called

    @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.Mock()
        command.returncode = 0
        mock_command.return_value = command
        assert self.mount_manager.is_mounted() is True

    @patch('kiwi.mount_manager.Command.run')
    def test_is_mounted_false(self, mock_command):
        command = mock.Mock()
        command.returncode = 1
        mock_command.return_value = command
        assert self.mount_manager.is_mounted() is False

    @patch('kiwi.mount_manager.Path.wipe')
    @patch('kiwi.mount_manager.MountManager.is_mounted')
    def test_destructor(self, mock_mounted, mock_wipe):
        self.mount_manager.mountpoint_created_by_mount_manager = True
        mock_mounted.return_value = False
        self.mount_manager.__del__()
        mock_wipe.assert_called_once_with('/some/mountpoint')
Example #9
0
class VolumeManagerBtrfs(VolumeManagerBase):
    """
    Implements btrfs sub-volume management

    :param list subvol_mount_list: list of mounted btrfs subvolumes
    :param object toplevel_mount: :class:`MountManager` for root mountpoint
    """
    def post_init(self, custom_args):
        """
        Post initialization method

        Store custom btrfs initialization arguments

        :param list custom_args: custom btrfs volume manager arguments
        """
        if custom_args:
            self.custom_args = custom_args
        else:
            self.custom_args = {}
        if 'root_label' not in self.custom_args:
            self.custom_args['root_label'] = 'ROOT'
        if 'root_is_snapshot' not in self.custom_args:
            self.custom_args['root_is_snapshot'] = False
        if 'root_is_readonly_snapshot' not in self.custom_args:
            self.custom_args['root_is_readonly_snapshot'] = False

        self.subvol_mount_list = []
        self.toplevel_mount = None
        self.toplevel_volume = None

    def setup(self, name=None):
        """
        Setup btrfs volume management

        In case of btrfs a toplevel(@) subvolume is created and marked
        as default volume. If snapshots are activated via the custom_args
        the setup method also created the @/.snapshots/1/snapshot
        subvolumes. There is no concept of a volume manager name, thus
        the name argument is not used for btrfs

        :param string name: unused
        """
        self.setup_mountpoint()

        filesystem = FileSystem(name='btrfs',
                                device_provider=MappedDevice(
                                    device=self.device, device_provider=self),
                                custom_args=self.custom_filesystem_args)
        filesystem.create_on_device(label=self.custom_args['root_label'])
        self.toplevel_mount = MountManager(device=self.device,
                                           mountpoint=self.mountpoint)
        self.toplevel_mount.mount(self.custom_filesystem_args['mount_options'])
        root_volume = self.mountpoint + '/@'
        Command.run(['btrfs', 'subvolume', 'create', root_volume])
        if self.custom_args['root_is_snapshot']:
            snapshot_volume = self.mountpoint + '/@/.snapshots'
            Command.run(['btrfs', 'subvolume', 'create', snapshot_volume])
            Path.create(snapshot_volume + '/1')
            snapshot = self.mountpoint + '/@/.snapshots/1/snapshot'
            Command.run(
                ['btrfs', 'subvolume', 'snapshot', root_volume, snapshot])
            self._set_default_volume('@/.snapshots/1/snapshot')
        else:
            self._set_default_volume('@')

    def create_volumes(self, filesystem_name):
        """
        Create configured btrfs subvolumes

        Any btrfs subvolume is of the same btrfs filesystem. There is no
        way to have different filesystems per btrfs subvolume. Thus
        the filesystem_name has no effect for btrfs

        :param string filesystem_name: unused
        """
        log.info('Creating %s sub volumes', filesystem_name)
        self.create_volume_paths_in_root_dir()

        canonical_volume_list = self.get_canonical_volume_list()
        if canonical_volume_list.full_size_volume:
            # put an eventual fullsize volume to the volume list
            # because there is no extra handling required for it on btrfs
            canonical_volume_list.volumes.append(
                canonical_volume_list.full_size_volume)

        for volume in canonical_volume_list.volumes:
            if volume.name == 'LVRoot':
                # the btrfs root volume named '@' has been created as
                # part of the setup procedure
                pass
            else:
                log.info('--> sub volume %s', volume.realpath)
                toplevel = self.mountpoint + '/@/'
                volume_parent_path = os.path.normpath(
                    toplevel + os.path.dirname(volume.realpath))
                if not os.path.exists(volume_parent_path):
                    Path.create(volume_parent_path)
                Command.run([
                    'btrfs', 'subvolume', 'create',
                    os.path.normpath(toplevel + volume.realpath)
                ])
                self.apply_attributes_on_volume(toplevel, volume)
                if self.custom_args['root_is_snapshot']:
                    snapshot = self.mountpoint + '/@/.snapshots/1/snapshot/'
                    volume_mount = MountManager(
                        device=self.device,
                        mountpoint=os.path.normpath(snapshot +
                                                    volume.realpath))
                    self.subvol_mount_list.append(volume_mount)

    def get_fstab(self, persistency_type='by-label', filesystem_name=None):
        """
        Implements creation of the fstab entries. The method
        returns a list of fstab compatible entries

        :param string persistency_type: by-label | by-uuid
        :param string filesystem_name: unused

        :return: list of fstab entries

        :rtype: list
        """
        fstab_entries = []
        mount_options = \
            self.custom_filesystem_args['mount_options'] or ['defaults']
        block_operation = BlockID(self.device)
        blkid_type = 'LABEL' if persistency_type == 'by-label' else 'UUID'
        device_id = block_operation.get_blkid(blkid_type)
        if self.custom_args['root_is_snapshot']:
            mount_entry_options = mount_options + ['subvol=@/.snapshots']
            fstab_entry = ' '.join([
                blkid_type + '=' + device_id, '/.snapshots', 'btrfs',
                ','.join(mount_entry_options), '0 0'
            ])
            fstab_entries.append(fstab_entry)
        for volume_mount in self.subvol_mount_list:
            subvol_name = self._get_subvol_name_from_mountpoint(volume_mount)
            mount_entry_options = mount_options + ['subvol=' + subvol_name]
            fstab_entry = ' '.join([
                blkid_type + '=' + device_id,
                subvol_name.replace('@', ''), 'btrfs',
                ','.join(mount_entry_options), '0 0'
            ])
            fstab_entries.append(fstab_entry)
        return fstab_entries

    def get_volumes(self):
        """
        Return dict of volumes

        :return: volumes dictionary

        :rtype: dict
        """
        volumes = {}
        for volume_mount in self.subvol_mount_list:
            subvol_name = self._get_subvol_name_from_mountpoint(volume_mount)
            subvol_options = ','.join(
                ['subvol=' + subvol_name] +
                self.custom_filesystem_args['mount_options'])
            volumes[subvol_name.replace('@', '')] = {
                'volume_options': subvol_options,
                'volume_device': volume_mount.device
            }
        return volumes

    def mount_volumes(self):
        """
        Mount btrfs subvolumes
        """

        self.toplevel_mount.mount(self.custom_filesystem_args['mount_options'])

        for volume_mount in self.subvol_mount_list:
            if self.volumes_mounted_initially:
                volume_mount.mountpoint = os.path.normpath(
                    volume_mount.mountpoint.replace(self.toplevel_volume, '',
                                                    1))
            if not os.path.exists(volume_mount.mountpoint):
                Path.create(volume_mount.mountpoint)
            subvol_name = self._get_subvol_name_from_mountpoint(volume_mount)
            subvol_options = ','.join(
                ['subvol=' + subvol_name] +
                self.custom_filesystem_args['mount_options'])
            volume_mount.mount(options=[subvol_options])

        self.volumes_mounted_initially = True

    def umount_volumes(self):
        """
        Umount btrfs subvolumes

        :return: True if all subvolumes are successfully unmounted

        :rtype: bool
        """
        all_volumes_umounted = True
        for volume_mount in reversed(self.subvol_mount_list):
            if volume_mount.is_mounted():
                if not volume_mount.umount():
                    all_volumes_umounted = False

        if all_volumes_umounted:
            if self.toplevel_mount.is_mounted():
                if not self.toplevel_mount.umount():
                    all_volumes_umounted = False

        return all_volumes_umounted

    def sync_data(self, exclude=None):
        """
        Sync data into btrfs filesystem

        If snapshots are activated the root filesystem is synced
        into the first snapshot

        :param list exclude: files to exclude from sync
        """
        if self.toplevel_mount:
            sync_target = self.mountpoint + '/@'
            if self.custom_args['root_is_snapshot']:
                sync_target = self.mountpoint + '/@/.snapshots/1/snapshot'
                self._create_snapshot_info(''.join(
                    [self.mountpoint, '/@/.snapshots/1/info.xml']))
            data = DataSync(self.root_dir, sync_target)
            data.sync_data(
                options=['-a', '-H', '-X', '-A', '--one-file-system'],
                exclude=exclude)

    def set_property_readonly_root(self):
        """
        Sets the root volume to be a readonly filesystem
        """
        root_is_snapshot = \
            self.custom_args['root_is_snapshot']
        root_is_readonly_snapshot = \
            self.custom_args['root_is_readonly_snapshot']
        if root_is_snapshot and root_is_readonly_snapshot:
            sync_target = self.mountpoint
            Command.run(
                ['btrfs', 'property', 'set', sync_target, 'ro', 'true'])

    def _set_default_volume(self, default_volume):
        subvolume_list_call = Command.run(
            ['btrfs', 'subvolume', 'list', self.mountpoint])
        for subvolume in subvolume_list_call.output.split('\n'):
            id_search = re.search('ID (\d+) .*path (.*)', subvolume)
            if id_search:
                volume_id = id_search.group(1)
                volume_path = id_search.group(2)
                if volume_path == default_volume:
                    Command.run([
                        'btrfs', 'subvolume', 'set-default', volume_id,
                        self.mountpoint
                    ])
                    self.toplevel_volume = default_volume
                    return

        raise KiwiVolumeRootIDError('Failed to find btrfs volume: %s' %
                                    default_volume)

    def _xml_pretty(self, toplevel_element):
        xml_data_unformatted = ElementTree.tostring(toplevel_element, 'utf-8')
        xml_data_domtree = minidom.parseString(xml_data_unformatted)
        return xml_data_domtree.toprettyxml(indent="    ")

    def _create_snapshot_info(self, filename):
        date_info = datetime.datetime.now()
        snapshot = ElementTree.Element('snapshot')

        snapshot_type = ElementTree.SubElement(snapshot, 'type')
        snapshot_type.text = 'single'

        snapshot_number = ElementTree.SubElement(snapshot, 'num')
        snapshot_number.text = '1'

        snapshot_description = ElementTree.SubElement(snapshot, 'description')
        snapshot_description.text = 'first root filesystem'

        snapshot_date = ElementTree.SubElement(snapshot, 'date')
        snapshot_date.text = date_info.strftime("%Y-%m-%d %H:%M:%S")

        with open(filename, 'w') as snapshot_info_file:
            snapshot_info_file.write(self._xml_pretty(snapshot))

    def _get_subvol_name_from_mountpoint(self, volume_mount):
        subvol_name = '/'.join(volume_mount.mountpoint.split('/')[3:])
        if self.toplevel_volume and self.toplevel_volume in subvol_name:
            subvol_name = subvol_name.replace(self.toplevel_volume, '')
        return os.path.normpath(os.sep.join(['@', subvol_name]))

    def __del__(self):
        if self.toplevel_mount:
            log.info('Cleaning up %s instance', type(self).__name__)
            if self.umount_volumes():
                Path.wipe(self.mountpoint)
Example #10
0
class TestMountManager:
    @fixture(autouse=True)
    def inject_fixtures(self, caplog):
        self._caplog = caplog

    def setup(self):
        self.mount_manager = MountManager(
            '/dev/some-device', '/some/mountpoint'
        )

    @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.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_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(
        self, mock_sleep, mock_mounted, mock_command
    ):
        mock_command.side_effect = Exception
        mock_mounted.return_value = True
        with self._caplog.at_level(logging.WARNING):
            assert self.mount_manager.umount() is False
        assert mock_command.call_args_list == [
            call(['umount', '/some/mountpoint']),
            call(['umount', '/some/mountpoint']),
            call(['umount', '/some/mountpoint'])
        ]

    @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
        )
Example #11
0
class TestMountManager:
    @fixture(autouse=True)
    def inject_fixtures(self, caplog):
        self._caplog = caplog

    def setup(self):
        self.mount_manager = MountManager('/dev/some-device',
                                          '/some/mountpoint')

    @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.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_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(self, mock_sleep, mock_mounted, mock_command):
        mock_command.side_effect = Exception
        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', '/some/mountpoint']),  # 6
            call(['umount', '/some/mountpoint']),  # 7
            call(['umount', '/some/mountpoint']),  # 8
            call(['umount', '/some/mountpoint']),  # 9
            call(['umount', '/some/mountpoint'])  # 10
        ]

    @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 Exception

        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 Exception
            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)