365 lines
12 KiB
Python
365 lines
12 KiB
Python
|
#
|
||
|
# The device tree scheduler
|
||
|
#
|
||
|
# Copyright (C) 2019 Red Hat, Inc.
|
||
|
#
|
||
|
# This copyrighted material is made available to anyone wishing to use,
|
||
|
# modify, copy, or redistribute it subject to the terms and conditions of
|
||
|
# the GNU General Public License v.2, or (at your option) any later version.
|
||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
# ANY WARRANTY expressed or implied, including the implied warranties of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||
|
# Public License for more details. You should have received a copy of the
|
||
|
# GNU General Public License along with this program; if not, write to the
|
||
|
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
|
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
||
|
# source code or documentation are not subject to the GNU General Public
|
||
|
# License and may only be used or replicated with the express permission of
|
||
|
# Red Hat, Inc.
|
||
|
#
|
||
|
from blivet import devicefactory
|
||
|
from blivet.size import Size
|
||
|
|
||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||
|
from pyanaconda.modules.common.structures.validation import ValidationReport
|
||
|
from pyanaconda.modules.storage.devicetree import DeviceTreeModule
|
||
|
from pyanaconda.modules.storage.partitioning.interactive.add_device import AddDeviceTask
|
||
|
from pyanaconda.modules.storage.partitioning.interactive.change_device import ChangeDeviceTask
|
||
|
from pyanaconda.modules.storage.partitioning.interactive.interactive_partitioning import \
|
||
|
InteractiveAutoPartitioningTask
|
||
|
from pyanaconda.modules.storage.partitioning.interactive.scheduler_interface import \
|
||
|
DeviceTreeSchedulerInterface
|
||
|
from pyanaconda.modules.storage.partitioning.interactive import utils
|
||
|
|
||
|
log = get_module_logger(__name__)
|
||
|
|
||
|
__all__ = ["DeviceTreeSchedulerModule"]
|
||
|
|
||
|
|
||
|
class DeviceTreeSchedulerModule(DeviceTreeModule):
|
||
|
"""The device tree scheduler."""
|
||
|
|
||
|
def for_publication(self):
|
||
|
"""Return a DBus representation."""
|
||
|
return DeviceTreeSchedulerInterface(self)
|
||
|
|
||
|
def is_device(self, device_name):
|
||
|
"""Is the specified device in the device tree?
|
||
|
|
||
|
It can recognize also hidden and incomplete devices.
|
||
|
|
||
|
:param device_name: a name of the device
|
||
|
:return: True or False
|
||
|
"""
|
||
|
device = self.storage.devicetree.get_device_by_name(
|
||
|
device_name, hidden=True, incomplete=True
|
||
|
)
|
||
|
|
||
|
return device is not None
|
||
|
|
||
|
def is_device_locked(self, device_name):
|
||
|
"""Is the specified device locked?
|
||
|
|
||
|
:param device_name: a name of the device
|
||
|
:return: True or False
|
||
|
"""
|
||
|
device = self._get_device(device_name)
|
||
|
return device.format.type == "luks" and device.format.exists
|
||
|
|
||
|
def is_device_editable(self, device_name):
|
||
|
"""Is the specified device editable?
|
||
|
|
||
|
:param device_name: a name of the device
|
||
|
:return: True or False
|
||
|
"""
|
||
|
device = self._get_device(device_name)
|
||
|
return devicefactory.get_device_type(device) is not None
|
||
|
|
||
|
def check_completeness(self, device_name):
|
||
|
"""Check that the specified device is complete.
|
||
|
|
||
|
:param device_name: a name of the device
|
||
|
:return: a validation report
|
||
|
"""
|
||
|
report = ValidationReport()
|
||
|
device = self._get_device(device_name)
|
||
|
message = utils.check_device_completeness(device)
|
||
|
|
||
|
if message:
|
||
|
report.error_messages.append(message)
|
||
|
|
||
|
return report
|
||
|
|
||
|
def get_default_file_system(self):
|
||
|
"""Get the default type of a filesystem.
|
||
|
|
||
|
:return: a filesystem name
|
||
|
"""
|
||
|
return self.storage.default_fstype
|
||
|
|
||
|
def get_default_luks_version(self):
|
||
|
"""Get the default version of LUKS.
|
||
|
|
||
|
:return: a version of LUKS
|
||
|
"""
|
||
|
return self.storage.default_luks_version
|
||
|
|
||
|
def get_container_free_space(self, container_name):
|
||
|
"""Get total free space in the specified container.
|
||
|
|
||
|
:param container_name: a name of the container
|
||
|
:return: a size in bytes
|
||
|
"""
|
||
|
container = self._get_device(container_name)
|
||
|
return Size(getattr(container, "free_space", 0)).get_bytes()
|
||
|
|
||
|
def generate_system_name(self):
|
||
|
"""Generate a name of the new installation.
|
||
|
|
||
|
:return: a translated string
|
||
|
"""
|
||
|
return utils.get_new_root_name()
|
||
|
|
||
|
def generate_system_data(self, boot_drive):
|
||
|
"""Generate the new installation data.
|
||
|
|
||
|
:param boot_drive: a name of the boot drive
|
||
|
:return: an instance of OSData
|
||
|
"""
|
||
|
root = utils.create_new_root(self.storage, boot_drive)
|
||
|
return self._get_os_data(root)
|
||
|
|
||
|
def generate_device_name(self, mount_point, format_type):
|
||
|
"""Get a suggestion for a device name.
|
||
|
|
||
|
:param mount_point: a mount point
|
||
|
:param format_type: a format type
|
||
|
:return: a generated device name
|
||
|
"""
|
||
|
return self.storage.suggest_device_name(
|
||
|
mountpoint=mount_point,
|
||
|
swap=bool(format_type == "swap")
|
||
|
)
|
||
|
|
||
|
def generate_container_name(self):
|
||
|
"""Get a suggestion for a container name.
|
||
|
|
||
|
:return: a generated container name
|
||
|
"""
|
||
|
return self._storage.suggest_container_name()
|
||
|
|
||
|
def generate_device_factory_request(self, device_name):
|
||
|
"""Generate a device factory request for the given device.
|
||
|
|
||
|
The request will reflect the current state of the device.
|
||
|
It can be modified and used to change the device.
|
||
|
|
||
|
:param device_name: a device name
|
||
|
:return: a device factory request
|
||
|
"""
|
||
|
device = self._get_device(device_name)
|
||
|
return utils.generate_device_factory_request(self.storage, device)
|
||
|
|
||
|
def generate_device_factory_permissions(self, request):
|
||
|
"""Generate device factory permissions for the given request.
|
||
|
|
||
|
The permissions will reflect which device attributes we are allowed
|
||
|
to change in the requested device.
|
||
|
|
||
|
:param request: a device factory request
|
||
|
:return: device factory permissions
|
||
|
"""
|
||
|
return utils.generate_device_factory_permissions(self.storage, request)
|
||
|
|
||
|
def generate_container_data(self, request):
|
||
|
"""Generate the container data for the device factory request.
|
||
|
|
||
|
:param request: a device factory request
|
||
|
"""
|
||
|
utils.generate_container_data(self.storage, request)
|
||
|
|
||
|
def update_container_data(self, request, container_name):
|
||
|
"""Update the container data in the device factory request.
|
||
|
|
||
|
:param request: a device factory request
|
||
|
:param container_name: a container name
|
||
|
"""
|
||
|
utils.update_container_data(self.storage, request, container_name)
|
||
|
|
||
|
def collect_new_devices(self, boot_drive):
|
||
|
"""Get all new devices in the device tree.
|
||
|
|
||
|
FIXME: Remove the boot drive option.
|
||
|
|
||
|
:param boot_drive: a name of the boot drive
|
||
|
:return: a list of device names
|
||
|
"""
|
||
|
return [d.name for d in utils.collect_new_devices(self.storage, boot_drive)]
|
||
|
|
||
|
def collect_unused_devices(self):
|
||
|
"""Collect all devices that are not used in existing or new installations.
|
||
|
|
||
|
:return: a list of device names
|
||
|
"""
|
||
|
return [d.name for d in utils.collect_unused_devices(self.storage)]
|
||
|
|
||
|
def collect_unused_mount_points(self):
|
||
|
"""Collect mount points that can be assigned to a device.
|
||
|
|
||
|
:return: a list of mount points
|
||
|
"""
|
||
|
return [m for m in utils.collect_mount_points() if m not in self.storage.mountpoints]
|
||
|
|
||
|
def collect_containers(self, device_type):
|
||
|
"""Collect containers of the given type.
|
||
|
|
||
|
:param device_type: a device type
|
||
|
:return: a list of container names
|
||
|
"""
|
||
|
return [c.name for c in utils.collect_containers(self.storage, device_type)]
|
||
|
|
||
|
def collect_supported_systems(self):
|
||
|
"""Collect supported existing or new installations.
|
||
|
|
||
|
:return: a list of data about found installations
|
||
|
"""
|
||
|
return list(map(self._get_os_data, utils.collect_roots(self.storage)))
|
||
|
|
||
|
def get_device_types_for_device(self, device_name):
|
||
|
"""Collect supported device types for the given device.
|
||
|
|
||
|
:param device_name: a device name
|
||
|
:return: a list of device types
|
||
|
"""
|
||
|
device = self._get_device(device_name)
|
||
|
return utils.collect_device_types(device)
|
||
|
|
||
|
def get_file_systems_for_device(self, device_name):
|
||
|
"""Get supported file system types for the given device.
|
||
|
|
||
|
:param device_name: a device name
|
||
|
:return: a list of file system names
|
||
|
"""
|
||
|
device = self._get_device(device_name)
|
||
|
return utils.collect_file_system_types(device)
|
||
|
|
||
|
def get_supported_raid_levels(self, device_type):
|
||
|
"""Get RAID levels for the specified device type.
|
||
|
|
||
|
:param device_type: a type of the device
|
||
|
:return: a list of RAID level names
|
||
|
"""
|
||
|
return sorted([level.name for level in utils.get_supported_raid_levels(device_type)])
|
||
|
|
||
|
def validate_mount_point(self, mount_point):
|
||
|
"""Validate the given mount point.
|
||
|
|
||
|
:param mount_point: a path to a mount point
|
||
|
:return: a validation report
|
||
|
"""
|
||
|
report = ValidationReport()
|
||
|
mount_points = self.storage.mountpoints.keys()
|
||
|
error = utils.validate_mount_point(mount_point, mount_points)
|
||
|
|
||
|
if error:
|
||
|
report.error_messages.append(error)
|
||
|
|
||
|
return report
|
||
|
|
||
|
def validate_raid_level(self, raid_level, num_members):
|
||
|
"""Validate the given RAID level.
|
||
|
|
||
|
:param raid_level: a RAID level name
|
||
|
:param num_members: a number of members
|
||
|
:return: a validation report
|
||
|
"""
|
||
|
report = ValidationReport()
|
||
|
raid_level = utils.get_raid_level_by_name(raid_level)
|
||
|
error = utils.validate_raid_level(raid_level, num_members)
|
||
|
|
||
|
if error:
|
||
|
report.error_messages.append(error)
|
||
|
|
||
|
return report
|
||
|
|
||
|
def validate_container_name(self, name):
|
||
|
"""Validate the given container name.
|
||
|
|
||
|
:param name: a container name
|
||
|
:return: a validation report
|
||
|
"""
|
||
|
report = ValidationReport()
|
||
|
error = utils.validate_container_name(self.storage, name)
|
||
|
|
||
|
if error:
|
||
|
report.error_messages.append(error)
|
||
|
|
||
|
return report
|
||
|
|
||
|
def validate_device_factory_request(self, request):
|
||
|
"""Validate the given device factory request.
|
||
|
|
||
|
:param request: a device factory request
|
||
|
:return: a validation report
|
||
|
"""
|
||
|
report = ValidationReport()
|
||
|
error = utils.validate_device_factory_request(self.storage, request)
|
||
|
|
||
|
if error:
|
||
|
report.error_messages.append(error)
|
||
|
|
||
|
return report
|
||
|
|
||
|
def add_device(self, request):
|
||
|
"""Add a new device to the storage model.
|
||
|
|
||
|
:param request: a device factory request
|
||
|
:raise: StorageConfigurationError if the device cannot be created
|
||
|
"""
|
||
|
task = AddDeviceTask(self.storage, request)
|
||
|
task.run()
|
||
|
|
||
|
def change_device(self, request, original_request):
|
||
|
"""Change a device in the storage model.
|
||
|
|
||
|
FIXME: Remove the original request from the arguments.
|
||
|
|
||
|
:param request: a device factory request
|
||
|
:param original_request: an original device factory request
|
||
|
:raise: StorageConfigurationError if the device cannot be changed
|
||
|
"""
|
||
|
device = self._get_device(request.device_spec)
|
||
|
task = ChangeDeviceTask(self.storage, device, request, original_request)
|
||
|
task.run()
|
||
|
|
||
|
def reset_device(self, device_name):
|
||
|
"""Reset the specified device in the storage model.
|
||
|
|
||
|
FIXME: Merge with destroy_device.
|
||
|
|
||
|
:param device_name: a name of the device
|
||
|
:raise: StorageConfigurationError in case of failure
|
||
|
"""
|
||
|
device = self._get_device(device_name)
|
||
|
utils.reset_device(self.storage, device)
|
||
|
|
||
|
def destroy_device(self, device_name):
|
||
|
"""Destroy the specified device in the storage model.
|
||
|
|
||
|
:param device_name: a name of the device
|
||
|
:raise: StorageConfigurationError in case of failure
|
||
|
"""
|
||
|
device = self._get_device(device_name)
|
||
|
utils.destroy_device(self.storage, device)
|
||
|
|
||
|
def schedule_partitions_with_task(self, request):
|
||
|
"""Schedule the partitioning actions.
|
||
|
|
||
|
Generate the automatic partitioning configuration
|
||
|
using the given request.
|
||
|
|
||
|
:param: a partitioning request
|
||
|
:return: a task
|
||
|
"""
|
||
|
return InteractiveAutoPartitioningTask(self.storage, request)
|