229 lines
9.3 KiB
Python
229 lines
9.3 KiB
Python
#
|
|
# 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.
|
|
#
|
|
import blivet.arch
|
|
from blivet.devices import iScsiDiskDevice
|
|
|
|
from pyanaconda.modules.storage.bootloader.base import BootLoaderError, \
|
|
is_on_non_ibft_sw_iscsi
|
|
from pyanaconda.core.configuration.anaconda import conf
|
|
from pyanaconda.core.constants import BOOTLOADER_ENABLED, BOOTLOADER_SKIPPED, \
|
|
BOOTLOADER_LOCATION_PARTITION
|
|
from pyanaconda.core.i18n import _
|
|
from pyanaconda.modules.common.constants.objects import BOOTLOADER
|
|
from pyanaconda.modules.common.constants.services import STORAGE
|
|
from pyanaconda.core.storage import device_matches
|
|
|
|
from pyanaconda.anaconda_loggers import get_module_logger
|
|
log = get_module_logger(__name__)
|
|
|
|
__all__ = ["setup_bootloader"]
|
|
|
|
|
|
def setup_bootloader(storage, dry_run=False):
|
|
"""Resolve and setup the bootloader configuration.
|
|
|
|
:param Blivet storage: an instance of the storage
|
|
:param bool dry_run: don't set devices if True
|
|
"""
|
|
executor = BootloaderExecutor()
|
|
executor.execute(storage=storage, dry_run=dry_run)
|
|
|
|
|
|
class BootloaderExecutor(object):
|
|
"""The executor of the bootloader command."""
|
|
|
|
def execute(self, storage, dry_run=False):
|
|
"""Execute the bootloader."""
|
|
log.debug("Execute the bootloader with dry run %s.", dry_run)
|
|
bootloader_proxy = STORAGE.get_proxy(BOOTLOADER)
|
|
|
|
# Skip bootloader for s390x image installation.
|
|
if blivet.arch.is_s390() \
|
|
and conf.target.is_image \
|
|
and bootloader_proxy.BootloaderMode == BOOTLOADER_ENABLED:
|
|
bootloader_proxy.BootloaderMode = BOOTLOADER_SKIPPED
|
|
|
|
# Is the bootloader enabled?
|
|
if bootloader_proxy.BootloaderMode != BOOTLOADER_ENABLED:
|
|
storage.bootloader.skip_bootloader = True
|
|
log.debug("Bootloader is not enabled, skipping.")
|
|
return
|
|
|
|
# Update the disk list. Disks are already sorted by Blivet.
|
|
storage.bootloader.set_disk_list([d for d in storage.disks if d.partitioned])
|
|
|
|
# Apply settings related to boot devices.
|
|
self._apply_location(storage, bootloader_proxy)
|
|
self._apply_drive_order(storage, bootloader_proxy, dry_run=dry_run)
|
|
self._apply_boot_drive(storage, bootloader_proxy, dry_run=dry_run)
|
|
|
|
# Set the stage2 and stage1 devices.
|
|
if not dry_run:
|
|
storage.bootloader.stage2_device = storage.boot_device
|
|
storage.bootloader.set_stage1_device(storage.devices)
|
|
|
|
def _apply_location(self, storage, bootloader_proxy):
|
|
"""Set the location."""
|
|
location = bootloader_proxy.PreferredLocation
|
|
log.debug("Applying bootloader location: %s", location)
|
|
|
|
storage.bootloader.set_preferred_stage1_type(
|
|
"boot" if location == BOOTLOADER_LOCATION_PARTITION else "mbr"
|
|
)
|
|
|
|
def _is_usable_disk(self, d):
|
|
"""Is the disk usable for the bootloader?
|
|
|
|
Throw out drives that don't exist or cannot be used
|
|
(iSCSI device on an s390 machine).
|
|
"""
|
|
return \
|
|
not d.format.hidden and \
|
|
not d.protected and \
|
|
not (blivet.arch.is_s390() and isinstance(d, iScsiDiskDevice)) and \
|
|
(not is_on_non_ibft_sw_iscsi(d) or conf.bootloader.nonibft_iscsi_boot)
|
|
|
|
def _get_usable_disks(self, storage):
|
|
"""Get a list of usable disks."""
|
|
return [d.name for d in storage.disks if self._is_usable_disk(d)]
|
|
|
|
def _apply_drive_order(self, storage, bootloader_proxy, dry_run=False):
|
|
"""Apply the drive order.
|
|
|
|
Drive specifications can contain | delimited variant specifications,
|
|
such as for example: "vd*|hd*|sd*"
|
|
|
|
So use the resolved disk identifiers returned by the device_matches()
|
|
function in place of the original specification but still remove the
|
|
specifications that don't match anything from the output kickstart to
|
|
keep existing --driveorder processing behavior.
|
|
"""
|
|
drive_order = bootloader_proxy.DriveOrder
|
|
usable_disks = set(self._get_usable_disks(storage))
|
|
valid_disks = []
|
|
|
|
for drive in drive_order[:]:
|
|
# Resolve disk identifiers.
|
|
matched_disks = device_matches(drive, devicetree=storage.devicetree, disks_only=True)
|
|
|
|
# Are any of the matched disks usable?
|
|
if any(d in usable_disks for d in matched_disks):
|
|
valid_disks.extend(matched_disks)
|
|
else:
|
|
drive_order.remove(drive)
|
|
log.warning("Requested drive %s in boot drive order doesn't exist "
|
|
"or cannot be used.", drive)
|
|
|
|
# Apply the drive order.
|
|
log.debug("Applying drive order: %s", valid_disks)
|
|
storage.bootloader.disk_order = valid_disks
|
|
|
|
# Update the module.
|
|
if not dry_run and bootloader_proxy.DriveOrder != drive_order:
|
|
bootloader_proxy.DriveOrder = drive_order
|
|
|
|
def _check_boot_drive(self, storage, boot_drive, usable_disks):
|
|
"""Check the specified boot drive."""
|
|
# Resolve the disk identifier.
|
|
matched_disks = device_matches(boot_drive, devicetree=storage.devicetree, disks_only=True)
|
|
|
|
if not matched_disks:
|
|
raise BootLoaderError(_("No match found for given boot drive "
|
|
"\"{}\".").format(boot_drive))
|
|
|
|
if len(matched_disks) > 1:
|
|
raise BootLoaderError(_("More than one match found for given boot drive "
|
|
"\"{}\".").format(boot_drive))
|
|
|
|
if matched_disks[0] not in usable_disks:
|
|
raise BootLoaderError(_("Requested boot drive \"{}\" doesn't exist or cannot "
|
|
"be used.").format(boot_drive))
|
|
|
|
def _find_drive_with_stage1(self, storage, usable_disks):
|
|
"""Find a drive with a valid stage1 device."""
|
|
# Search for valid stage1 devices.
|
|
for device in storage.devices:
|
|
if not storage.bootloader.is_valid_stage1_device(device):
|
|
continue
|
|
|
|
# Search for usable disks.
|
|
for disk in device.disks:
|
|
drive = disk.name
|
|
|
|
if drive not in usable_disks:
|
|
continue
|
|
|
|
log.debug("Found a drive with a valid stage1: %s", drive)
|
|
return drive
|
|
|
|
# No usable disk found.
|
|
log.debug("No usable drive with a valid stage1 was found.")
|
|
return None
|
|
|
|
def _get_boot_drive(self, storage, bootloader_proxy):
|
|
"""Get the boot drive.
|
|
|
|
When bootloader doesn't have --boot-drive parameter then use this logic as fallback:
|
|
|
|
1) If present, use the first valid disk from driveorder parameter.
|
|
2) If present and usable, use a disk where a valid stage1 device is placed.
|
|
3) Use the first usable disk from Blivet if there is one.
|
|
4) Raise an exception.
|
|
"""
|
|
boot_drive = bootloader_proxy.Drive
|
|
drive_order = storage.bootloader.disk_order
|
|
usable_disks_list = self._get_usable_disks(storage)
|
|
usable_disks_set = set(usable_disks_list)
|
|
|
|
# Use a disk from --boot-drive.
|
|
if boot_drive:
|
|
log.debug("Use the requested boot drive.")
|
|
self._check_boot_drive(storage, boot_drive, usable_disks_set)
|
|
return boot_drive
|
|
|
|
# Or use the first disk from --driveorder.
|
|
if drive_order:
|
|
log.debug("Use the first usable drive from the drive order.")
|
|
return drive_order[0]
|
|
|
|
# Or find a disk with a valid stage1 device.
|
|
found_drive = self._find_drive_with_stage1(storage, usable_disks_set)
|
|
if found_drive:
|
|
log.debug("Use a usable drive with a valid stage1 device.")
|
|
return found_drive
|
|
|
|
# Or use the first usable drive.
|
|
if usable_disks_list:
|
|
log.debug("Use the first usable drive.")
|
|
return usable_disks_list[0]
|
|
|
|
# Or raise an exception.
|
|
raise BootLoaderError("No usable boot drive was found.")
|
|
|
|
def _apply_boot_drive(self, storage, bootloader_proxy, dry_run=False):
|
|
"""Apply the boot drive."""
|
|
boot_drive = self._get_boot_drive(storage, bootloader_proxy)
|
|
log.debug("Using a boot drive: %s", boot_drive)
|
|
|
|
# Apply the boot drive.
|
|
drive = storage.devicetree.resolve_device(boot_drive)
|
|
storage.bootloader.stage1_disk = drive
|
|
|
|
# Update the bootloader module.
|
|
if not dry_run and bootloader_proxy.Drive != boot_drive:
|
|
bootloader_proxy.Drive = boot_drive
|