anaconda/anaconda-40.22.3.13/pyanaconda/modules/storage/storage.py
2024-11-14 21:39:56 -08:00

400 lines
14 KiB
Python

#
# Kickstart module for the storage.
#
# Copyright (C) 2018 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 pyanaconda.core.signal import Signal
from pyanaconda.core.dbus import DBus
from blivet import __version__ as blivet_version
from pyanaconda.modules.common.base import KickstartService
from pyanaconda.modules.common.constants.services import STORAGE
from pyanaconda.modules.common.containers import TaskContainer
from pyanaconda.modules.common.errors.storage import InvalidStorageError
from pyanaconda.modules.common.structures.requirement import Requirement
from pyanaconda.modules.common.submodule_manager import SubmoduleManager
from pyanaconda.modules.storage.bootloader import BootloaderModule
from pyanaconda.modules.storage.checker import StorageCheckerModule
from pyanaconda.modules.storage.dasd import DASDModule
from pyanaconda.modules.storage.devicetree import DeviceTreeModule, create_storage
from pyanaconda.modules.storage.disk_initialization import DiskInitializationModule
from pyanaconda.modules.storage.disk_selection import DiskSelectionModule
from pyanaconda.modules.storage.fcoe import FCOEModule
from pyanaconda.modules.storage.installation import MountFilesystemsTask, CreateStorageLayoutTask, \
WriteConfigurationTask
from pyanaconda.modules.storage.iscsi import ISCSIModule
from pyanaconda.modules.storage.kickstart import StorageKickstartSpecification
from pyanaconda.modules.storage.nvme import NVMEModule
from pyanaconda.modules.storage.partitioning.constants import PartitioningMethod
from pyanaconda.modules.storage.partitioning.factory import PartitioningFactory
from pyanaconda.modules.storage.partitioning.validate import StorageValidateTask
from pyanaconda.modules.storage.platform import platform
from pyanaconda.modules.storage.reset import ScanDevicesTask
from pyanaconda.modules.storage.snapshot import SnapshotModule
from pyanaconda.modules.storage.storage_interface import StorageInterface
from pyanaconda.modules.storage.storage_subscriber import StorageSubscriberModule
from pyanaconda.modules.storage.teardown import UnmountFilesystemsTask, TeardownDiskImagesTask
from pyanaconda.modules.storage.zfcp import ZFCPModule
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
class StorageService(KickstartService):
"""The Storage service."""
def __init__(self):
super().__init__()
# The storage model.
self._current_storage = None
self._storage_playground = None
self.storage_changed = Signal()
# The created partitioning modules.
self._created_partitioning = []
self.created_partitioning_changed = Signal()
# The applied partitioning module.
self._applied_partitioning = None
self.applied_partitioning_changed = Signal()
self.partitioning_reset = Signal()
# Initialize modules.
self._modules = SubmoduleManager()
self._storage_checker_module = StorageCheckerModule()
self._modules.add_module(self._storage_checker_module)
self._device_tree_module = DeviceTreeModule()
self._modules.add_module(self._device_tree_module)
self._disk_init_module = DiskInitializationModule()
self._modules.add_module(self._disk_init_module)
self._disk_selection_module = DiskSelectionModule()
self._modules.add_module(self._disk_selection_module)
self._snapshot_module = SnapshotModule()
self._modules.add_module(self._snapshot_module)
self._bootloader_module = BootloaderModule()
self._modules.add_module(self._bootloader_module)
self._fcoe_module = FCOEModule()
self._modules.add_module(self._fcoe_module)
self._iscsi_module = ISCSIModule()
self._modules.add_module(self._iscsi_module)
self._nvme_module = NVMEModule()
self._modules.add_module(self._nvme_module)
self._dasd_module = DASDModule()
self._modules.add_module(self._dasd_module)
self._zfcp_module = ZFCPModule()
self._modules.add_module(self._zfcp_module)
# Connect modules to signals.
for module in self._modules:
if isinstance(module, StorageSubscriberModule):
self.storage_changed.connect(module.on_storage_changed)
self._disk_init_module.format_unrecognized_enabled_changed.connect(
self._dasd_module.on_format_unrecognized_enabled_changed
)
self._disk_init_module.format_ldl_enabled_changed.connect(
self._dasd_module.on_format_ldl_enabled_changed
)
self._disk_selection_module.protected_devices_changed.connect(
self.on_protected_devices_changed
)
# After connecting modules to signals, create the initial
# storage model. It will be propagated to all modules.
self._set_storage(create_storage())
def publish(self):
"""Publish the module."""
TaskContainer.set_namespace(STORAGE.namespace)
self._modules.publish_modules()
DBus.publish_object(STORAGE.object_path, StorageInterface(self))
DBus.register_service(STORAGE.service_name)
@property
def kickstart_specification(self):
"""Return the kickstart specification."""
return StorageKickstartSpecification
def process_kickstart(self, data):
"""Process the kickstart data."""
# Process the kickstart data in modules.
self._modules.process_kickstart(data)
# Set the default filesystem type.
if data.autopart.autopart and data.autopart.fstype:
self.storage.set_default_fstype(data.autopart.fstype)
# Create a new partitioning module.
partitioning_method = PartitioningFactory.get_method_for_kickstart(data)
if partitioning_method:
partitioning_module = self.create_partitioning(partitioning_method)
partitioning_module.process_kickstart(data)
def setup_kickstart(self, data):
"""Set up the kickstart data."""
self._modules.setup_kickstart(data)
if self.applied_partitioning:
self.applied_partitioning.setup_kickstart(data)
def generate_kickstart(self):
"""Generate kickstart string representation of this module's data
Adds Blivet version to the output because most of the strings come from Blivet anyway.
"""
return "# Generated using Blivet version {}\n{}".format(
blivet_version,
super().generate_kickstart()
)
@property
def storage(self):
"""The storage model.
:return: an instance of Blivet
"""
if self._storage_playground:
return self._storage_playground
if not self._current_storage:
self._set_storage(create_storage())
return self._current_storage
def _set_storage(self, storage):
"""Set the current storage model.
The current storage is the latest model of
the system's storage configuration created
by scanning all devices.
:param storage: a storage
"""
self._current_storage = storage
if self._storage_playground:
return
self.storage_changed.emit(storage)
log.debug("The storage model has changed.")
def _set_storage_playground(self, storage):
"""Set the storage playground.
The storage playground is a model of a valid
partitioned storage configuration, that can be
used for an installation.
:param storage: a storage or None
"""
self._storage_playground = storage
if storage is None:
storage = self.storage
self.storage_changed.emit(storage)
log.debug("The storage model has changed.")
def on_protected_devices_changed(self, protected_devices):
"""Update the protected devices in the storage model."""
if not self._current_storage:
return
self.storage.protect_devices(protected_devices)
def scan_devices_with_task(self):
"""Scan all devices with a task.
We will reset a copy of the current storage model
and switch the models if the reset is successful.
:return: a task
"""
# Copy the storage.
storage = self.storage.copy()
# Set up the storage.
storage.ignored_disks = self._disk_selection_module.ignored_disks
storage.exclusive_disks = self._disk_selection_module.exclusive_disks
storage.protected_devices = self._disk_selection_module.protected_devices
storage.disk_images = self._disk_selection_module.disk_images
# Create the task.
task = ScanDevicesTask(storage)
task.succeeded_signal.connect(lambda: self._set_storage(storage))
return task
def create_partitioning(self, method: PartitioningMethod):
"""Create a new partitioning.
Allowed values:
AUTOMATIC
CUSTOM
MANUAL
INTERACTIVE
BLIVET
:param PartitioningMethod method: a partitioning method
:return: a partitioning module
"""
module = PartitioningFactory.create_partitioning(method)
# Update the module.
module.on_storage_changed(
self._current_storage
)
module.on_selected_disks_changed(
self._disk_selection_module.selected_disks
)
# Connect the callbacks to signals.
self.storage_changed.connect(
module.on_storage_changed
)
self.partitioning_reset.connect(
module.on_partitioning_reset
)
self._disk_selection_module.selected_disks_changed.connect(
module.on_selected_disks_changed
)
# Update the list of modules.
self._add_created_partitioning(module)
return module
@property
def created_partitioning(self):
"""List of all created partitioning modules."""
return self._created_partitioning
def _add_created_partitioning(self, module):
"""Add a created partitioning module."""
self._created_partitioning.append(module)
self.created_partitioning_changed.emit(module)
log.debug("Created the partitioning %s.", module)
def apply_partitioning(self, module):
"""Apply a partitioning.
:param module: a partitioning module
:raise: InvalidStorageError if the partitioning is not valid
"""
# Validate the partitioning.
storage = module.storage.copy()
task = StorageValidateTask(storage)
report = task.run()
if not report.is_valid():
raise InvalidStorageError(" ".join(report.error_messages))
# Apply the partitioning.
self._set_storage_playground(storage)
self._set_applied_partitioning(module)
@property
def applied_partitioning(self):
"""The applied partitioning."""
return self._applied_partitioning
def _set_applied_partitioning(self, module):
"""Set the applied partitioning.
:param module: a partitioning module or None
"""
self._applied_partitioning = module
self.applied_partitioning_changed.emit()
if module is None:
module = "NONE"
log.debug("The partitioning %s is applied.", module)
def reset_partitioning(self):
"""Reset the partitioning."""
self._set_storage_playground(None)
self._set_applied_partitioning(None)
self.partitioning_reset.emit()
def collect_requirements(self):
"""Return installation requirements for this module.
:return: a list of requirements
"""
requirements = []
# Add the platform requirements.
for name in platform.packages:
requirements.append(Requirement.for_package(
name, reason="Required for the platform."
))
# Add the storage requirements.
for name in self.storage.packages:
requirements.append(Requirement.for_package(
name, reason="Required to manage storage devices."
))
# Add other requirements, for example for bootloader.
requirements.extend(self._modules.collect_requirements())
return requirements
def install_with_tasks(self):
"""Returns installation tasks of this module.
:returns: list of installation tasks
"""
storage = self.storage
return [
CreateStorageLayoutTask(storage),
MountFilesystemsTask(storage)
]
def write_configuration_with_task(self):
"""Write the storage configuration with a task.
FIXME: This is a temporary workaround.
:return: an installation task
"""
return WriteConfigurationTask(self.storage)
def teardown_with_tasks(self):
"""Returns teardown tasks for this module.
:return: a list installation tasks
"""
storage = self.storage
return [
UnmountFilesystemsTask(storage),
TeardownDiskImagesTask(storage)
]