320 lines
11 KiB
Python
320 lines
11 KiB
Python
|
#
|
||
|
# Copyright (C) 2020 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.errors import StorageError, InconsistentPVSectorSize
|
||
|
from blivet.size import Size
|
||
|
|
||
|
from dasbus.structure import compare_data
|
||
|
|
||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||
|
from pyanaconda.core.i18n import _
|
||
|
from pyanaconda.modules.common.errors.configuration import StorageConfigurationError
|
||
|
from pyanaconda.modules.common.structures.device_factory import DeviceFactoryRequest
|
||
|
from pyanaconda.modules.common.task import Task
|
||
|
from pyanaconda.modules.storage.constants import INCONSISTENT_SECTOR_SIZES_SUGGESTIONS
|
||
|
from pyanaconda.modules.storage.partitioning.interactive.utils import destroy_device, \
|
||
|
get_device_factory_arguments, revert_reformat, resize_device, reformat_device, \
|
||
|
validate_label, change_encryption, rename_container
|
||
|
|
||
|
log = get_module_logger(__name__)
|
||
|
|
||
|
__all__ = ["ChangeDeviceTask"]
|
||
|
|
||
|
|
||
|
class ChangeDeviceTask(Task):
|
||
|
"""A task for changing a device in the device tree."""
|
||
|
|
||
|
def __init__(self, storage, device, request: DeviceFactoryRequest,
|
||
|
original_request: DeviceFactoryRequest):
|
||
|
"""Create a task.
|
||
|
|
||
|
FIXME: Remove device and original request from the arguments.
|
||
|
|
||
|
:param storage: an instance of Blivet
|
||
|
:param device: a device to change
|
||
|
:param request: a device factory request
|
||
|
:param original_request: an original device factory request
|
||
|
"""
|
||
|
super().__init__()
|
||
|
self._storage = storage
|
||
|
self._device = device
|
||
|
self._request = request
|
||
|
self._original_request = original_request
|
||
|
|
||
|
@property
|
||
|
def name(self):
|
||
|
"""Name of this task."""
|
||
|
return "Change a device"
|
||
|
|
||
|
def run(self):
|
||
|
"""Change a device in the device tree.
|
||
|
|
||
|
:raise: StorageConfigurationError if the device cannot be changed
|
||
|
"""
|
||
|
log.debug("Change device: %s", self._request)
|
||
|
|
||
|
# Nothing to do. Skip.
|
||
|
if compare_data(self._request, self._original_request):
|
||
|
log.debug("Nothing to change.")
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
# Change the container.
|
||
|
self._rename_container()
|
||
|
|
||
|
# Change or replace the device.
|
||
|
if not self._device.raw_device.exists:
|
||
|
self._replace_device()
|
||
|
else:
|
||
|
self._change_device()
|
||
|
|
||
|
except InconsistentPVSectorSize as e:
|
||
|
self._handle_storage_error(e, "\n\n".join([
|
||
|
_("Failed to change a device."),
|
||
|
str(e).strip(),
|
||
|
_(INCONSISTENT_SECTOR_SIZES_SUGGESTIONS)
|
||
|
]))
|
||
|
except StorageError as e:
|
||
|
self._handle_storage_error(e, str(e))
|
||
|
|
||
|
def _handle_storage_error(self, exception, message):
|
||
|
"""Handle the storage error."""
|
||
|
log.error("Failed to change a device: %s", message)
|
||
|
raise StorageConfigurationError(message) from exception
|
||
|
|
||
|
def _rename_container(self):
|
||
|
"""Rename the existing container."""
|
||
|
container_spec = self._request.container_spec
|
||
|
container_name = self._request.container_name
|
||
|
|
||
|
# Nothing to do.
|
||
|
if not container_spec or container_spec == container_name:
|
||
|
return
|
||
|
|
||
|
container = self._storage.devicetree.resolve_device(container_spec)
|
||
|
|
||
|
# Container doesn't exist.
|
||
|
if not container:
|
||
|
return
|
||
|
|
||
|
log.debug("Changing container name: %s", container_name)
|
||
|
|
||
|
try:
|
||
|
rename_container(self._storage, container, container_name)
|
||
|
except StorageError as e:
|
||
|
log.error("Invalid container name: %s", e)
|
||
|
raise StorageError(str(e)) from e
|
||
|
|
||
|
def _replace_device(self):
|
||
|
"""Replace the nonexistent device with a new one.
|
||
|
|
||
|
If something has changed but the device does not exist,
|
||
|
there is no need to schedule actions on the device. It
|
||
|
is only necessary to create a new device object which
|
||
|
reflects the current choices.
|
||
|
"""
|
||
|
log.debug("Replacing a nonexistent device.")
|
||
|
device = self._device
|
||
|
|
||
|
if self._should_remove_device():
|
||
|
# Remove the current device.
|
||
|
destroy_device(self._storage, device)
|
||
|
|
||
|
# We don't want to pass the device if we removed it.
|
||
|
self._request.device_spec = ""
|
||
|
|
||
|
# Create a new device.
|
||
|
log.debug("Creating a new device.")
|
||
|
arguments = get_device_factory_arguments(self._storage, self._request)
|
||
|
device = self._storage.factory_device(**arguments)
|
||
|
|
||
|
# Update the device.
|
||
|
self._device = device
|
||
|
|
||
|
def _should_remove_device(self):
|
||
|
"""Should we remove the current device?"""
|
||
|
new_request = self._request
|
||
|
old_request = self._original_request
|
||
|
|
||
|
# Check the device type.
|
||
|
if old_request.device_type != new_request.device_type:
|
||
|
return True
|
||
|
|
||
|
# Check the container name.
|
||
|
if old_request.container_name and new_request.container_name != old_request.container_name:
|
||
|
return True
|
||
|
|
||
|
return False
|
||
|
|
||
|
def _change_device(self):
|
||
|
"""Change the configuration of the existing device."""
|
||
|
log.debug("Modifying an existing device.")
|
||
|
|
||
|
self._revert_device_reformat()
|
||
|
self._change_device_size()
|
||
|
|
||
|
if self._should_reformat_device():
|
||
|
self._change_device_encryption()
|
||
|
self._change_device_format()
|
||
|
else:
|
||
|
self._change_device_label()
|
||
|
self._change_device_mount_point()
|
||
|
|
||
|
self._change_device_name()
|
||
|
|
||
|
def _revert_device_reformat(self):
|
||
|
"""Revert reformat of the device."""
|
||
|
if self._request.reformat:
|
||
|
return
|
||
|
|
||
|
log.debug("Reverting device reformat.")
|
||
|
revert_reformat(self._storage, self._device)
|
||
|
|
||
|
def _change_device_size(self):
|
||
|
"""Resize the device."""
|
||
|
size = Size(self._request.device_size)
|
||
|
original_size = Size(self._original_request.device_size)
|
||
|
|
||
|
if size == original_size:
|
||
|
return
|
||
|
|
||
|
log.debug("Changing device size: %s", size)
|
||
|
resize_device(self._storage, self._device, size, original_size)
|
||
|
|
||
|
def _should_reformat_device(self):
|
||
|
"""Should we reformat the device?
|
||
|
|
||
|
:return: True of False
|
||
|
"""
|
||
|
if not self._request.reformat:
|
||
|
return False
|
||
|
|
||
|
if self._device.format.exists:
|
||
|
return True
|
||
|
|
||
|
if self._original_request.device_encrypted != self._request.device_encrypted:
|
||
|
return True
|
||
|
|
||
|
if self._original_request.luks_version != self._request.luks_version:
|
||
|
return True
|
||
|
|
||
|
if self._original_request.format_type != self._request.format_type:
|
||
|
return True
|
||
|
|
||
|
return False
|
||
|
|
||
|
def _change_device_encryption(self):
|
||
|
"""Change the device encryption."""
|
||
|
storage = self._storage
|
||
|
device = self._device
|
||
|
encrypted = self._request.device_encrypted
|
||
|
luks_version = self._request.luks_version
|
||
|
|
||
|
if self._original_request.device_encrypted != encrypted:
|
||
|
log.debug("Changing device encryption: %s", encrypted)
|
||
|
device = change_encryption(
|
||
|
storage=storage,
|
||
|
device=device,
|
||
|
encrypted=encrypted,
|
||
|
luks_version=luks_version
|
||
|
)
|
||
|
elif encrypted and self._original_request.luks_version != luks_version:
|
||
|
log.debug("Changing LUKS version: %s", luks_version)
|
||
|
|
||
|
# LUKS version cannot be easily changed,
|
||
|
# so remove the current LUKS device.
|
||
|
device = change_encryption(
|
||
|
storage=storage,
|
||
|
device=device,
|
||
|
encrypted=False,
|
||
|
luks_version=luks_version
|
||
|
)
|
||
|
|
||
|
# And create a new one with the requested
|
||
|
# LUKS version.
|
||
|
device = change_encryption(
|
||
|
storage=storage,
|
||
|
device=device,
|
||
|
encrypted=True,
|
||
|
luks_version=luks_version
|
||
|
)
|
||
|
|
||
|
self._device = device
|
||
|
|
||
|
def _change_device_format(self):
|
||
|
"""Change the device format."""
|
||
|
log.debug("Changing device format: %s", self._request.format_type)
|
||
|
|
||
|
reformat_device(
|
||
|
storage=self._storage,
|
||
|
device=self._device,
|
||
|
fstype=self._request.format_type,
|
||
|
mountpoint=self._request.mount_point,
|
||
|
label=self._request.label
|
||
|
)
|
||
|
|
||
|
def _change_device_label(self):
|
||
|
"""Change the device label."""
|
||
|
label = self._request.label
|
||
|
|
||
|
if self._original_request.label == label:
|
||
|
return
|
||
|
|
||
|
if not hasattr(self._device.format, "label"):
|
||
|
log.warning("Cannot set a label to the current format.")
|
||
|
return
|
||
|
|
||
|
if self._device.format.exists:
|
||
|
log.warning("Cannot relabel already existing file system.")
|
||
|
return
|
||
|
|
||
|
if validate_label(label, self._device.format):
|
||
|
log.warning("Cannot set an invalid label.")
|
||
|
return
|
||
|
|
||
|
log.debug("Changing device label: %s", label)
|
||
|
self._device.format.label = label
|
||
|
|
||
|
def _change_device_mount_point(self):
|
||
|
"""Change the device mount point."""
|
||
|
mount_point = self._request.mount_point
|
||
|
|
||
|
if not mount_point:
|
||
|
return
|
||
|
|
||
|
if self._original_request.mount_point == mount_point:
|
||
|
return
|
||
|
|
||
|
log.debug("Changing device mount point: %s", mount_point)
|
||
|
self._device.format.mountpoint = mount_point
|
||
|
|
||
|
def _change_device_name(self):
|
||
|
"""Change the device name."""
|
||
|
name = self._request.device_name
|
||
|
original_name = self._original_request.device_name
|
||
|
|
||
|
if name == original_name:
|
||
|
return
|
||
|
|
||
|
log.debug("Changing device name: %s", name)
|
||
|
|
||
|
try:
|
||
|
self._device.raw_device.name = name
|
||
|
except ValueError as e:
|
||
|
log.error("Invalid device name: %s", e)
|
||
|
raise StorageError(str(e)) from e
|