def reset(self, cleanup_only=False): """ Reset storage configuration to reflect actual system state. This will cancel any queued actions and rescan from scratch but not clobber user-obtained information like passphrases, iscsi config, &c :keyword cleanup_only: prepare the tree only to deactivate devices :type cleanup_only: bool See :meth:`devicetree.Devicetree.populate` for more information about the cleanup_only keyword argument. """ # set up the disk images if conf.target.is_image: self.setup_disk_images() # save passphrases for luks devices so we don't have to reprompt for device in self.devices: if device.format.type == "luks" and device.format.exists: self.save_passphrase(device) super().reset(cleanup_only=cleanup_only) # Protect devices from teardown. self._mark_protected_devices() self.devicetree.teardown_all() self.fsset = FSSet(self.devicetree) # Clear out attributes that refer to devices that are no longer in the tree. self.bootloader.reset() self.roots = [] self.roots = find_existing_installations(self.devicetree) self.dump_state("initial")
def __init__(self): super().__init__() self.protected_devices = [] self._escrow_certificates = {} self._bootloader = None self.__luks_devs = {} self.fsset = FSSet(self.devicetree) self._short_product_name = shortProductName self._default_luks_version = DEFAULT_LUKS_VERSION # Set the default filesystem type. self.set_default_fstype(conf.storage.file_system_type or self.default_fstype) # Set the default LUKS version. self.set_default_luks_version(conf.storage.luks_version or self.default_luks_version)
def setUp(self): """Set up the test.""" self.maxDiff = None self.devicetree = DeviceTree() self.fsset = FSSet(self.devicetree)
class FSSetTestCase(unittest.TestCase): """Test the class that represents a set of filesystems.""" def setUp(self): """Set up the test.""" self.maxDiff = None self.devicetree = DeviceTree() self.fsset = FSSet(self.devicetree) def _add_device(self, device): """Add a device to the device tree.""" self.devicetree._add_device(device) def _get_mount_points(self, devices): """Get mount points of the given devices.""" return [getattr(d.format, "mountpoint", None) for d in devices] def _get_format_types(self, devices): """Get format types of the given devices.""" return [d.format.type for d in devices] def test_system_filesystems(self): """Test the system_filesystems property.""" devices = self.fsset.system_filesystems # There are some devices in the list. assert devices # The devices are always the same. assert devices == self.fsset.system_filesystems @patch("pyanaconda.modules.storage.devicetree.fsset.platform", X86()) def test_collect_filesystems(self): """Test the collect_filesystems method.""" devices = self.fsset.collect_filesystems() mount_points = self._get_mount_points(devices) format_types = self._get_format_types(devices) assert mount_points == [ '/dev', '/dev/pts', '/dev/shm', '/proc', '/proc/bus/usb', '/run', '/sys', '/sys/fs/selinux', '/tmp', ] assert format_types == [ 'bind', 'devpts', 'tmpfs', 'proc', 'usbfs', 'bind', 'sysfs', 'selinuxfs', 'tmpfs', ] @patch("pyanaconda.modules.storage.devicetree.fsset.platform", EFI()) def test_collect_filesystems_efi(self): """Test the collect_filesystems method with EFI.""" devices = self.fsset.collect_filesystems() mount_points = self._get_mount_points(devices) format_types = self._get_format_types(devices) assert mount_points == [ '/dev', '/dev/pts', '/dev/shm', '/proc', '/proc/bus/usb', '/run', '/sys', '/sys/firmware/efi/efivars', '/sys/fs/selinux', '/tmp', ] assert format_types == [ 'bind', 'devpts', 'tmpfs', 'proc', 'usbfs', 'bind', 'sysfs', 'efivarfs', 'selinuxfs', 'tmpfs', ] @patch("pyanaconda.modules.storage.devicetree.fsset.platform", X86()) def test_collect_filesystems_tmp(self): """Test the collect_filesystems method with /tmp.""" self._add_device(StorageDevice("dev1", fmt=get_format("ext4", mountpoint="/tmp"))) devices = self.fsset.collect_filesystems() mount_points = self._get_mount_points(devices) format_types = self._get_format_types(devices) assert mount_points == [ '/dev', '/dev/pts', '/dev/shm', '/proc', '/proc/bus/usb', '/run', '/sys', '/sys/fs/selinux', '/tmp', ] assert format_types == [ 'bind', 'devpts', 'tmpfs', 'proc', 'usbfs', 'bind', 'sysfs', 'selinuxfs', 'ext4', ] @patch("pyanaconda.modules.storage.devicetree.fsset.platform", X86()) def test_collect_filesystems_extra(self): """Test the collect_filesystems method with additional devices.""" self._add_device(StorageDevice("dev1", fmt=get_format("ext4", mountpoint="/boot"))) self._add_device(StorageDevice("dev2", fmt=get_format("ext4", mountpoint="/"))) self._add_device(StorageDevice("dev3", fmt=get_format("ext4", mountpoint="/home"))) self._add_device(StorageDevice("dev4", fmt=get_format("swap"))) self._add_device(StorageDevice("dev5", fmt=get_format("swap"))) devices = self.fsset.collect_filesystems() mount_points = self._get_mount_points(devices) format_types = self._get_format_types(devices) assert mount_points == [ None, None, '/', '/boot', '/dev', '/dev/pts', '/dev/shm', '/home', '/proc', '/proc/bus/usb', '/run', '/sys', '/sys/fs/selinux', '/tmp', ] assert format_types == [ 'swap', 'swap', 'ext4', 'ext4', 'bind', 'devpts', 'tmpfs', 'ext4', 'proc', 'usbfs', 'bind', 'sysfs', 'selinuxfs', 'tmpfs', ]
class InstallerStorage(Blivet): """ Top-level class for managing installer-related storage configuration. """ def __init__(self): super().__init__() self.protected_devices = [] self._escrow_certificates = {} self._bootloader = None self.fsset = FSSet(self.devicetree) self._short_product_name = shortProductName self._default_luks_version = DEFAULT_LUKS_VERSION # Set the default filesystem type. self.set_default_fstype(conf.storage.file_system_type or self.default_fstype) # Set the default LUKS version. self.set_default_luks_version(conf.storage.luks_version or self.default_luks_version) @property def bootloader(self): if self._bootloader is None: self._bootloader = BootLoaderFactory.create_boot_loader() return self._bootloader @property def boot_device(self): root_device = self.mountpoints.get("/") dev = self.mountpoints.get("/boot", root_device) return dev @property def default_boot_fstype(self): """The default filesystem type for the boot partition.""" if self.default_fstype in self.bootloader.stage2_format_types: return self.default_fstype return self.bootloader.stage2_format_types[0] @property def default_luks_version(self): """The default LUKS version.""" return self._default_luks_version def set_default_luks_version(self, version): """Set the default LUKS version. :param version: a string with LUKS version :raises: ValueError on invalid input """ log.debug("trying to set new default luks version to '%s'", version) self._check_valid_luks_version(version) self._default_luks_version = version def _check_valid_luks_version(self, version): get_format("luks", luks_version=version) def get_fstype(self, mountpoint=None): """ Return the default filesystem type based on mountpoint. """ fstype = super().get_fstype(mountpoint=mountpoint) if mountpoint == "/boot": fstype = self.default_boot_fstype return fstype def get_escrow_certificate(self, url): """Get the escrow certificate. :param url: an URL of the certificate :return: a content of the certificate """ if not url: return None certificate = self._escrow_certificates.get(url, None) if not certificate: certificate = download_escrow_certificate(url) self._escrow_certificates[url] = certificate return certificate @property def mountpoints(self): return self.fsset.mountpoints @property def root_device(self): return self.fsset.root_device def get_file_system_free_space(self, mount_points=("/", "/usr")): """Get total file system free space on the given mount points. Calculates total free space in / and /usr, by default. :param mount_points: a list of mount points :return: a total size """ free = Size(0) btrfs_volumes = [] for mount_point in mount_points: device = self.mountpoints.get(mount_point) if not device: continue # don't count the size of btrfs volumes repeatedly when multiple # subvolumes are present if isinstance(device, BTRFSSubVolumeDevice): if device.volume in btrfs_volumes: continue else: btrfs_volumes.append(device.volume) if device.format.exists: free += device.format.free else: free += device.format.free_space_estimate(device.size) return free def get_disk_free_space(self, disks=None): """Get total free space on the given disks. Calculates free space available for use. :param disks: a list of disks or None :return: a total size """ # Use all disks in the device tree by default. if disks is None: disks = self.disks # Get a list of disks with supported disk labels. disks = self._skip_unsupported_disk_labels(disks) # Get the dictionary of free spaces for each disk. snapshot = self.get_free_space(disks) # Calculate the total free space. return sum((disk_free for disk_free, fs_free in snapshot.values()), Size(0)) def get_disk_reclaimable_space(self, disks=None): """Get total reclaimable space on the given disks. Calculates free space unavailable but reclaimable from existing partitions. :param disks: a list of disks or None :return: a total size """ # Use all disks in the device tree by default. if disks is None: disks = self.disks # Get a list of disks with supported disk labels. disks = self._skip_unsupported_disk_labels(disks) # Get the dictionary of free spaces for each disk. snapshot = self.get_free_space(disks) # Calculate the total reclaimable free space. return sum((fs_free for disk_free, fs_free in snapshot.values()), Size(0)) def _skip_unsupported_disk_labels(self, disks): """Get a list of disks with supported disk labels. Skip initialized disks with disk labels that are not supported on this platform. :param disks: a list of disks :return: a list of disks with supported disk labels """ label_types = set(DiskLabel.get_platform_label_types()) def is_supported(disk): if disk.format.type is None: return True return disk.format.type == "disklabel" \ and disk.format.label_type in label_types return list(filter(is_supported, disks)) def reset(self, cleanup_only=False): """ Reset storage configuration to reflect actual system state. This will cancel any queued actions and rescan from scratch but not clobber user-obtained information like passphrases, iscsi config, &c :keyword cleanup_only: prepare the tree only to deactivate devices :type cleanup_only: bool See :meth:`devicetree.Devicetree.populate` for more information about the cleanup_only keyword argument. """ # set up the disk images if conf.target.is_image: self.setup_disk_images() # save passphrases for luks devices so we don't have to reprompt for device in self.devices: if device.format.type == "luks" and device.format.exists: self.save_passphrase(device) super().reset(cleanup_only=cleanup_only) # Protect devices from teardown. self._mark_protected_devices() self.devicetree.teardown_all() self.fsset = FSSet(self.devicetree) # Clear out attributes that refer to devices that are no longer in the tree. self.bootloader.reset() self.roots = [] self.roots = find_existing_installations(self.devicetree) self.dump_state("initial") def _mark_protected_devices(self): """Mark protected devices. If a device is protected, mark it as such now. Once the tree has been populated, devices' protected attribute is how we will identify protected devices. """ protected = [] # Resolve the protected device specs to devices. for spec in self.protected_devices: dev = self.devicetree.resolve_device(spec) if dev is not None: log.debug("Protected device spec %s resolved to %s.", spec, dev.name) protected.append(dev) # Find the live backing device and its parents. live_device = find_live_backing_device(self.devicetree) if live_device: log.debug("Resolved live device to %s.", live_device.name) protected.append(live_device) protected.extend(live_device.parents) # For image installation setup_disk_images method marks all local # storage disks as ignored so they are protected from teardown. # Here we protect also cdrom devices from tearing down that, in case of # cdroms, involves unmounting which is undesirable (see bug #1671713). protected.extend(dev for dev in self.devicetree.devices if dev.type == "cdrom") # Protect also all devices with an iso9660 file system. It will protect # NVDIMM devices that can be used only as an installation source anyway # (see the bug #1856264). protected.extend(dev for dev in self.devicetree.devices if dev.format.type == "iso9660") # Mark the collected devices as protected. for dev in protected: log.debug("Marking device %s as protected.", dev.name) dev.protected = True def protect_devices(self, protected_names): """Protect given devices. :param protected_names: a list of device names """ protected = set(protected_names) unprotected = set(self.protected_devices) # Mark unprotected devices. # Skip devices that should stay protected. for spec in unprotected - protected: device = self.devicetree.resolve_device(spec) if device: log.debug("Marking device %s as unprotected.", device.name) device.protected = False # Mark protected devices. # Skip devices that are already protected. for spec in protected - unprotected: device = self.devicetree.resolve_device(spec) if device: log.debug("Marking device %s as protected.", device.name) device.protected = True # Update the list. self.protected_devices = protected_names @property def usable_disks(self): """Disks that can be used for the installation. :return: a list of disks """ # Get all devices. devices = self.devicetree.devices # Add the hidden devices. if conf.target.is_image: devices += [ d for d in self.devicetree._hidden if d.name in self.devicetree.disk_images ] else: devices += self.devicetree._hidden # Filter out the usable disks. disks = [] for d in devices: if d.is_disk and not d.format.hidden and not d.protected: # Unformatted DASDs are detected with a size of 0, but they should # still show up as valid disks if this function is called, since we # can still use them; anaconda will know how to handle them, so they # don't need to be ignored anymore. if d.type == "dasd": disks.append(d) elif d.size > 0 and d.media_present: disks.append(d) # Remove duplicate names from the list. return sorted(set(disks), key=lambda d: d.name) def select_disks(self, selected_names): """Select disks that should be used for the installation. Hide usable disks that are not selected. :param selected_names: a list of disk names """ for disk in self.usable_disks: if disk.name not in selected_names: if disk in self.devices: self.devicetree.hide(disk) else: if disk not in self.devices: self.devicetree.unhide(disk) def _get_hostname(self): """Return a hostname.""" ignored_hostnames = {None, "", 'localhost', 'localhost.localdomain'} network_proxy = NETWORK.get_proxy() hostname = network_proxy.Hostname if hostname in ignored_hostnames: hostname = network_proxy.GetCurrentHostname() if hostname in ignored_hostnames: hostname = None return hostname def _get_container_name_template(self, prefix=None): """Return a template for suggest_container_name method.""" prefix = prefix or "" # make sure prefix is a string instead of None # try to create a device name incorporating the hostname hostname = self._get_hostname() if hostname: template = "%s_%s" % (prefix, hostname.split('.')[0].lower()) template = self.safe_device_name(template) else: template = prefix if conf.target.is_image: template = "%s_image" % template return template def turn_on_swap(self): self.fsset.turn_on_swap(root_path=conf.target.system_root) def mount_filesystems(self): root_path = conf.target.physical_root # Mount the root and the filesystems. self.fsset.mount_filesystems(root_path=root_path) # Set up the sysroot. set_system_root(root_path) def umount_filesystems(self, swapoff=True): # Unmount the root and the filesystems. self.fsset.umount_filesystems(swapoff=swapoff) # Unmount the sysroot. set_system_root(None) def parse_fstab(self, chroot=None): self.fsset.parse_fstab(chroot=chroot) def make_mtab(self, chroot=None): path = "/etc/mtab" target = "/proc/self/mounts" chroot = chroot or conf.target.system_root path = os.path.normpath("%s/%s" % (chroot, path)) if os.path.islink(path): # return early if the mtab symlink is already how we like it current_target = os.path.normpath( os.path.dirname(path) + "/" + os.readlink(path)) if current_target == target: return if os.path.exists(path): os.unlink(path) os.symlink(target, path) def add_fstab_swap(self, device): """ Add swap device to the list of swaps that should appear in the fstab. :param device: swap device that should be added to the list :type device: blivet.devices.StorageDevice instance holding a swap format """ self.fsset.add_fstab_swap(device) def set_fstab_swaps(self, devices): """ Set swap devices that should appear in the fstab. :param devices: iterable providing devices that should appear in the fstab :type devices: iterable providing blivet.devices.StorageDevice instances holding a swap format """ self.fsset.set_fstab_swaps(devices)