forked from rpms/leapp-repository
ee57901913
- Fix the calculation of the required free space on each partitions/volume for the upgrade transactions - Create source overlay images with dynamic sizes to optimize disk space consumption - Update GRUB2 when /boot resides on multiple devices aggregated in RAID - Use new leapp CLI API which provides better report summary output - Introduce possibility to add (custom) kernel drivers to initramfs - Detect and report use of deprecated Xorg drivers - Fix the generation of the report about hybrid images - Inhibit the upgrade when unsupported x86-64 microarchitecture is detected - Minor improvements and fixes of various reports - Requires leapp-framework 4.0 - Update leapp data files - Resolves: rhbz#2140011, rhbz#2144304, rhbz#2174095, rhbz#2219544, rhbz#2215997
664 lines
27 KiB
Diff
664 lines
27 KiB
Diff
From 2ba44076625e35aabfd2a1f9e45b2934f99f1e8d Mon Sep 17 00:00:00 2001
|
|
From: Matej Matuska <mmatuska@redhat.com>
|
|
Date: Mon, 20 Mar 2023 13:27:46 +0100
|
|
Subject: [PATCH 34/42] Update Grub on component drives if /boot is on md
|
|
device
|
|
|
|
On BIOS systems, previously, if /boot was on md device such as RAID
|
|
consisting of multiple partitions on different MBR/GPT partitioned
|
|
drives, the part of Grub residing in the 512 Mb after MBR was only
|
|
updated for one of the drives. Similar situation occurred on GPT
|
|
partitioned drives and the BIOS boot partition. This resulted in
|
|
outdated GRUB on the remaining drives which could cause the system to be
|
|
unbootable.
|
|
|
|
Now, Grub is updated on all the component devices of an md array if Grub
|
|
was already installed on them before the upgrade.
|
|
|
|
Jira: OAMG-7835
|
|
BZ#2219544
|
|
BZ#2140011
|
|
---
|
|
.../common/actors/checkgrubcore/actor.py | 7 +-
|
|
.../checkgrubcore/tests/test_checkgrubcore.py | 9 +-
|
|
.../common/actors/scangrubdevice/actor.py | 11 +--
|
|
.../tests/test_scangrubdevice.py | 35 +++++++
|
|
.../common/actors/updategrubcore/actor.py | 8 +-
|
|
.../libraries/updategrubcore.py | 48 ++++++----
|
|
.../tests/test_updategrubcore.py | 39 ++++++--
|
|
repos/system_upgrade/common/libraries/grub.py | 28 ++++++
|
|
.../system_upgrade/common/libraries/mdraid.py | 48 ++++++++++
|
|
.../common/libraries/tests/test_grub.py | 71 ++++++++++++--
|
|
.../common/libraries/tests/test_mdraid.py | 94 +++++++++++++++++++
|
|
.../system_upgrade/common/models/grubinfo.py | 12 +++
|
|
12 files changed, 358 insertions(+), 52 deletions(-)
|
|
create mode 100644 repos/system_upgrade/common/actors/scangrubdevice/tests/test_scangrubdevice.py
|
|
create mode 100644 repos/system_upgrade/common/libraries/mdraid.py
|
|
create mode 100644 repos/system_upgrade/common/libraries/tests/test_mdraid.py
|
|
|
|
diff --git a/repos/system_upgrade/common/actors/checkgrubcore/actor.py b/repos/system_upgrade/common/actors/checkgrubcore/actor.py
|
|
index 6aa99797..ae9e53ef 100644
|
|
--- a/repos/system_upgrade/common/actors/checkgrubcore/actor.py
|
|
+++ b/repos/system_upgrade/common/actors/checkgrubcore/actor.py
|
|
@@ -32,7 +32,7 @@ class CheckGrubCore(Actor):
|
|
grub_info = next(self.consume(GrubInfo), None)
|
|
if not grub_info:
|
|
raise StopActorExecutionError('Actor did not receive any GrubInfo message.')
|
|
- if grub_info.orig_device_name:
|
|
+ if grub_info.orig_devices:
|
|
create_report([
|
|
reporting.Title(
|
|
'GRUB2 core will be automatically updated during the upgrade'
|
|
@@ -45,8 +45,9 @@ class CheckGrubCore(Actor):
|
|
create_report([
|
|
reporting.Title('Leapp could not identify where GRUB2 core is located'),
|
|
reporting.Summary(
|
|
- 'We assumed GRUB2 core is located on the same device as /boot, however Leapp could not '
|
|
- 'detect GRUB2 on the device. GRUB2 core needs to be updated maually on legacy (BIOS) systems. '
|
|
+ 'We assumed GRUB2 core is located on the same device(s) as /boot, '
|
|
+ 'however Leapp could not detect GRUB2 on the device(s). '
|
|
+ 'GRUB2 core needs to be updated maually on legacy (BIOS) systems. '
|
|
),
|
|
reporting.Severity(reporting.Severity.HIGH),
|
|
reporting.Groups([reporting.Groups.BOOT]),
|
|
diff --git a/repos/system_upgrade/common/actors/checkgrubcore/tests/test_checkgrubcore.py b/repos/system_upgrade/common/actors/checkgrubcore/tests/test_checkgrubcore.py
|
|
index fe15b65b..b834f9fe 100644
|
|
--- a/repos/system_upgrade/common/actors/checkgrubcore/tests/test_checkgrubcore.py
|
|
+++ b/repos/system_upgrade/common/actors/checkgrubcore/tests/test_checkgrubcore.py
|
|
@@ -1,18 +1,17 @@
|
|
-import pytest
|
|
-
|
|
-from leapp.exceptions import StopActorExecutionError
|
|
from leapp.libraries.common.config import mock_configs
|
|
from leapp.models import FirmwareFacts, GrubInfo
|
|
from leapp.reporting import Report
|
|
|
|
NO_GRUB = 'Leapp could not identify where GRUB2 core is located'
|
|
+GRUB = 'GRUB2 core will be automatically updated during the upgrade'
|
|
|
|
|
|
def test_actor_update_grub(current_actor_context):
|
|
current_actor_context.feed(FirmwareFacts(firmware='bios'))
|
|
- current_actor_context.feed(GrubInfo(orig_device_name='/dev/vda'))
|
|
+ current_actor_context.feed(GrubInfo(orig_devices=['/dev/vda', '/dev/vdb']))
|
|
current_actor_context.run(config_model=mock_configs.CONFIG)
|
|
assert current_actor_context.consume(Report)
|
|
+ assert current_actor_context.consume(Report)[0].report['title'].startswith(GRUB)
|
|
|
|
|
|
def test_actor_no_grub_device(current_actor_context):
|
|
@@ -31,6 +30,6 @@ def test_actor_with_efi(current_actor_context):
|
|
|
|
def test_s390x(current_actor_context):
|
|
current_actor_context.feed(FirmwareFacts(firmware='bios'))
|
|
- current_actor_context.feed(GrubInfo(orig_device_name='/dev/vda'))
|
|
+ current_actor_context.feed(GrubInfo(orig_devices=['/dev/vda', '/dev/vdb']))
|
|
current_actor_context.run(config_model=mock_configs.CONFIG_S390X)
|
|
assert not current_actor_context.consume(Report)
|
|
diff --git a/repos/system_upgrade/common/actors/scangrubdevice/actor.py b/repos/system_upgrade/common/actors/scangrubdevice/actor.py
|
|
index a12739e1..cb6be7ea 100644
|
|
--- a/repos/system_upgrade/common/actors/scangrubdevice/actor.py
|
|
+++ b/repos/system_upgrade/common/actors/scangrubdevice/actor.py
|
|
@@ -7,7 +7,7 @@ from leapp.tags import FactsPhaseTag, IPUWorkflowTag
|
|
|
|
class ScanGrubDeviceName(Actor):
|
|
"""
|
|
- Find the name of the block device where GRUB is located
|
|
+ Find the name of the block devices where GRUB is located
|
|
"""
|
|
|
|
name = 'scan_grub_device_name'
|
|
@@ -19,8 +19,7 @@ class ScanGrubDeviceName(Actor):
|
|
if architecture.matches_architecture(architecture.ARCH_S390X):
|
|
return
|
|
|
|
- device_name = grub.get_grub_device()
|
|
- if device_name:
|
|
- self.produce(GrubInfo(orig_device_name=device_name))
|
|
- else:
|
|
- self.produce(GrubInfo())
|
|
+ devices = grub.get_grub_devices()
|
|
+ grub_info = GrubInfo(orig_devices=devices)
|
|
+ grub_info.orig_device_name = devices[0] if len(devices) == 1 else None
|
|
+ self.produce(grub_info)
|
|
diff --git a/repos/system_upgrade/common/actors/scangrubdevice/tests/test_scangrubdevice.py b/repos/system_upgrade/common/actors/scangrubdevice/tests/test_scangrubdevice.py
|
|
new file mode 100644
|
|
index 00000000..0114d717
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/actors/scangrubdevice/tests/test_scangrubdevice.py
|
|
@@ -0,0 +1,35 @@
|
|
+from leapp.libraries.common import grub
|
|
+from leapp.libraries.common.config import mock_configs
|
|
+from leapp.models import GrubInfo
|
|
+
|
|
+
|
|
+def _get_grub_devices_mocked():
|
|
+ return ['/dev/vda', '/dev/vdb']
|
|
+
|
|
+
|
|
+def test_actor_scan_grub_device(current_actor_context, monkeypatch):
|
|
+ monkeypatch.setattr(grub, 'get_grub_devices', _get_grub_devices_mocked)
|
|
+ current_actor_context.run(config_model=mock_configs.CONFIG)
|
|
+ info = current_actor_context.consume(GrubInfo)
|
|
+ assert info and info[0].orig_devices == ['/dev/vda', '/dev/vdb']
|
|
+ assert len(info) == 1, 'Expected just one GrubInfo message'
|
|
+ assert not info[0].orig_device_name
|
|
+
|
|
+
|
|
+def test_actor_scan_grub_device_one(current_actor_context, monkeypatch):
|
|
+
|
|
+ def _get_grub_devices_mocked():
|
|
+ return ['/dev/vda']
|
|
+
|
|
+ monkeypatch.setattr(grub, 'get_grub_devices', _get_grub_devices_mocked)
|
|
+ current_actor_context.run(config_model=mock_configs.CONFIG)
|
|
+ info = current_actor_context.consume(GrubInfo)
|
|
+ assert info and info[0].orig_devices == ['/dev/vda']
|
|
+ assert len(info) == 1, 'Expected just one GrubInfo message'
|
|
+ assert info[0].orig_device_name == '/dev/vda'
|
|
+
|
|
+
|
|
+def test_actor_scan_grub_device_s390x(current_actor_context, monkeypatch):
|
|
+ monkeypatch.setattr(grub, 'get_grub_devices', _get_grub_devices_mocked)
|
|
+ current_actor_context.run(config_model=mock_configs.CONFIG_S390X)
|
|
+ assert not current_actor_context.consume(GrubInfo)
|
|
diff --git a/repos/system_upgrade/common/actors/updategrubcore/actor.py b/repos/system_upgrade/common/actors/updategrubcore/actor.py
|
|
index 4545bad6..ac9aa829 100644
|
|
--- a/repos/system_upgrade/common/actors/updategrubcore/actor.py
|
|
+++ b/repos/system_upgrade/common/actors/updategrubcore/actor.py
|
|
@@ -21,8 +21,8 @@ class UpdateGrubCore(Actor):
|
|
def process(self):
|
|
ff = next(self.consume(FirmwareFacts), None)
|
|
if ff and ff.firmware == 'bios':
|
|
- grub_dev = grub.get_grub_device()
|
|
- if grub_dev:
|
|
- update_grub_core(grub_dev)
|
|
+ grub_devs = grub.get_grub_devices()
|
|
+ if grub_devs:
|
|
+ update_grub_core(grub_devs)
|
|
else:
|
|
- api.current_logger().warning('Leapp could not detect GRUB on {}'.format(grub_dev))
|
|
+ api.current_logger().warning('Leapp could not detect GRUB devices')
|
|
diff --git a/repos/system_upgrade/common/actors/updategrubcore/libraries/updategrubcore.py b/repos/system_upgrade/common/actors/updategrubcore/libraries/updategrubcore.py
|
|
index 22ee3372..2bdad929 100644
|
|
--- a/repos/system_upgrade/common/actors/updategrubcore/libraries/updategrubcore.py
|
|
+++ b/repos/system_upgrade/common/actors/updategrubcore/libraries/updategrubcore.py
|
|
@@ -1,35 +1,43 @@
|
|
from leapp import reporting
|
|
-from leapp.exceptions import StopActorExecution
|
|
from leapp.libraries.stdlib import api, CalledProcessError, config, run
|
|
|
|
|
|
-def update_grub_core(grub_dev):
|
|
+def update_grub_core(grub_devs):
|
|
"""
|
|
Update GRUB core after upgrade from RHEL7 to RHEL8
|
|
|
|
On legacy systems, GRUB core does not get automatically updated when GRUB packages
|
|
are updated.
|
|
"""
|
|
- cmd = ['grub2-install', grub_dev]
|
|
- if config.is_debug():
|
|
- cmd += ['-v']
|
|
- try:
|
|
- run(cmd)
|
|
- except CalledProcessError as err:
|
|
- reporting.create_report([
|
|
- reporting.Title('GRUB core update failed'),
|
|
- reporting.Summary(str(err)),
|
|
- reporting.Groups([reporting.Groups.BOOT]),
|
|
- reporting.Severity(reporting.Severity.HIGH),
|
|
- reporting.Remediation(
|
|
- hint='Please run "grub2-install <GRUB_DEVICE>" manually after upgrade'
|
|
- )
|
|
- ])
|
|
- api.current_logger().warning('GRUB core update on {} failed'.format(grub_dev))
|
|
- raise StopActorExecution()
|
|
+
|
|
+ successful = []
|
|
+ failed = []
|
|
+ for dev in grub_devs:
|
|
+ cmd = ['grub2-install', dev]
|
|
+ if config.is_debug():
|
|
+ cmd += ['-v']
|
|
+ try:
|
|
+ run(cmd)
|
|
+ except CalledProcessError as err:
|
|
+ api.current_logger().warning('GRUB core update on {} failed: {}'.format(dev, err))
|
|
+ failed.append(dev)
|
|
+ continue
|
|
+
|
|
+ successful.append(dev)
|
|
+
|
|
+ reporting.create_report([
|
|
+ reporting.Title('GRUB core update failed'),
|
|
+ reporting.Summary('Leapp failed to update GRUB on {}'.format(', '.join(failed))),
|
|
+ reporting.Groups([reporting.Groups.BOOT]),
|
|
+ reporting.Severity(reporting.Severity.HIGH),
|
|
+ reporting.Remediation(
|
|
+ hint='Please run "grub2-install <GRUB_DEVICE>" manually after upgrade'
|
|
+ )
|
|
+ ])
|
|
+
|
|
reporting.create_report([
|
|
reporting.Title('GRUB core successfully updated'),
|
|
- reporting.Summary('GRUB core on {} was successfully updated'.format(grub_dev)),
|
|
+ reporting.Summary('GRUB core on {} was successfully updated'.format(', '.join(successful))),
|
|
reporting.Groups([reporting.Groups.BOOT]),
|
|
reporting.Severity(reporting.Severity.INFO)
|
|
])
|
|
diff --git a/repos/system_upgrade/common/actors/updategrubcore/tests/test_updategrubcore.py b/repos/system_upgrade/common/actors/updategrubcore/tests/test_updategrubcore.py
|
|
index e65807a2..fe0cca50 100644
|
|
--- a/repos/system_upgrade/common/actors/updategrubcore/tests/test_updategrubcore.py
|
|
+++ b/repos/system_upgrade/common/actors/updategrubcore/tests/test_updategrubcore.py
|
|
@@ -1,7 +1,6 @@
|
|
import pytest
|
|
|
|
from leapp import reporting
|
|
-from leapp.exceptions import StopActorExecution
|
|
from leapp.libraries.actor import updategrubcore
|
|
from leapp.libraries.common import testutils
|
|
from leapp.libraries.stdlib import api, CalledProcessError
|
|
@@ -32,21 +31,45 @@ class run_mocked(object):
|
|
raise_call_error(args)
|
|
|
|
|
|
-def test_update_grub(monkeypatch):
|
|
+@pytest.mark.parametrize('devices', [['/dev/vda'], ['/dev/vda', '/dev/vdb']])
|
|
+def test_update_grub(monkeypatch, devices):
|
|
monkeypatch.setattr(reporting, "create_report", testutils.create_report_mocked())
|
|
monkeypatch.setattr(updategrubcore, 'run', run_mocked())
|
|
- updategrubcore.update_grub_core('/dev/vda')
|
|
+ updategrubcore.update_grub_core(devices)
|
|
assert reporting.create_report.called
|
|
- assert UPDATE_OK_TITLE == reporting.create_report.report_fields['title']
|
|
+ assert UPDATE_OK_TITLE == reporting.create_report.reports[1]['title']
|
|
+ assert all(dev in reporting.create_report.reports[1]['summary'] for dev in devices)
|
|
|
|
|
|
-def test_update_grub_failed(monkeypatch):
|
|
+@pytest.mark.parametrize('devices', [['/dev/vda'], ['/dev/vda', '/dev/vdb']])
|
|
+def test_update_grub_failed(monkeypatch, devices):
|
|
monkeypatch.setattr(reporting, "create_report", testutils.create_report_mocked())
|
|
monkeypatch.setattr(updategrubcore, 'run', run_mocked(raise_err=True))
|
|
- with pytest.raises(StopActorExecution):
|
|
- updategrubcore.update_grub_core('/dev/vda')
|
|
+ updategrubcore.update_grub_core(devices)
|
|
assert reporting.create_report.called
|
|
- assert UPDATE_FAILED_TITLE == reporting.create_report.report_fields['title']
|
|
+ assert UPDATE_FAILED_TITLE == reporting.create_report.reports[0]['title']
|
|
+ assert all(dev in reporting.create_report.reports[0]['summary'] for dev in devices)
|
|
+
|
|
+
|
|
+def test_update_grub_success_and_fail(monkeypatch):
|
|
+ monkeypatch.setattr(reporting, "create_report", testutils.create_report_mocked())
|
|
+
|
|
+ def run_mocked(args):
|
|
+ if args == ['grub2-install', '/dev/vdb']:
|
|
+ raise_call_error(args)
|
|
+ else:
|
|
+ assert args == ['grub2-install', '/dev/vda']
|
|
+
|
|
+ monkeypatch.setattr(updategrubcore, 'run', run_mocked)
|
|
+
|
|
+ devices = ['/dev/vda', '/dev/vdb']
|
|
+ updategrubcore.update_grub_core(devices)
|
|
+
|
|
+ assert reporting.create_report.called
|
|
+ assert UPDATE_FAILED_TITLE == reporting.create_report.reports[0]['title']
|
|
+ assert '/dev/vdb' in reporting.create_report.reports[0]['summary']
|
|
+ assert UPDATE_OK_TITLE == reporting.create_report.reports[1]['title']
|
|
+ assert '/dev/vda' in reporting.create_report.reports[1]['summary']
|
|
|
|
|
|
def test_update_grub_negative(current_actor_context):
|
|
diff --git a/repos/system_upgrade/common/libraries/grub.py b/repos/system_upgrade/common/libraries/grub.py
|
|
index f6b00f65..79b3be39 100644
|
|
--- a/repos/system_upgrade/common/libraries/grub.py
|
|
+++ b/repos/system_upgrade/common/libraries/grub.py
|
|
@@ -1,7 +1,9 @@
|
|
import os
|
|
|
|
from leapp.exceptions import StopActorExecution
|
|
+from leapp.libraries.common import mdraid
|
|
from leapp.libraries.stdlib import api, CalledProcessError, run
|
|
+from leapp.utils.deprecation import deprecated
|
|
|
|
|
|
def has_grub(blk_dev):
|
|
@@ -59,6 +61,32 @@ def get_boot_partition():
|
|
return boot_partition
|
|
|
|
|
|
+def get_grub_devices():
|
|
+ """
|
|
+ Get block devices where GRUB is located. We assume GRUB is on the same device
|
|
+ as /boot partition is. In case that device is an md (Multiple Device) device, all
|
|
+ of the component devices of such a device are considered.
|
|
+
|
|
+ :return: Devices where GRUB is located
|
|
+ :rtype: list
|
|
+ """
|
|
+ boot_device = get_boot_partition()
|
|
+ devices = []
|
|
+ if mdraid.is_mdraid_dev(boot_device):
|
|
+ component_devs = mdraid.get_component_devices(boot_device)
|
|
+ blk_devs = [blk_dev_from_partition(dev) for dev in component_devs]
|
|
+ # remove duplicates as there might be raid on partitions on the same drive
|
|
+ # even if that's very unusual
|
|
+ devices = sorted(list(set(blk_devs)))
|
|
+ else:
|
|
+ devices.append(blk_dev_from_partition(boot_device))
|
|
+
|
|
+ have_grub = [dev for dev in devices if has_grub(dev)]
|
|
+ api.current_logger().info('GRUB is installed on {}'.format(",".join(have_grub)))
|
|
+ return have_grub
|
|
+
|
|
+
|
|
+@deprecated(since='2023-06-23', message='This function has been replaced by get_grub_devices')
|
|
def get_grub_device():
|
|
"""
|
|
Get block device where GRUB is located. We assume GRUB is on the same device
|
|
diff --git a/repos/system_upgrade/common/libraries/mdraid.py b/repos/system_upgrade/common/libraries/mdraid.py
|
|
new file mode 100644
|
|
index 00000000..5eb89c56
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/libraries/mdraid.py
|
|
@@ -0,0 +1,48 @@
|
|
+from leapp.libraries.stdlib import api, CalledProcessError, run
|
|
+
|
|
+
|
|
+def is_mdraid_dev(dev):
|
|
+ """
|
|
+ Check if a given device is an md (Multiple Device) device
|
|
+
|
|
+ It is expected that the "mdadm" command is available,
|
|
+ if it's not it is assumed the device is not an md device.
|
|
+
|
|
+ :return: True if the device is an md device, False otherwise
|
|
+ :raises CalledProcessError: If an error occurred
|
|
+ """
|
|
+ fail_msg = 'Could not check if device "{}" is an md device: {}'
|
|
+ try:
|
|
+ result = run(['mdadm', '--query', dev])
|
|
+ except OSError as err:
|
|
+ api.current_logger().warning(fail_msg.format(dev, err))
|
|
+ return False
|
|
+ except CalledProcessError as err:
|
|
+ err.message = fail_msg.format(dev, err)
|
|
+ raise # let the calling actor handle the exception
|
|
+
|
|
+ return '--detail' in result['stdout']
|
|
+
|
|
+
|
|
+def get_component_devices(raid_dev):
|
|
+ """
|
|
+ Get list of component devices in an md (Multiple Device) array
|
|
+
|
|
+ :return: The list of component devices or None in case of error
|
|
+ :raises ValueError: If the device is not an mdraid device
|
|
+ """
|
|
+ try:
|
|
+ # using both --verbose and --brief for medium verbosity
|
|
+ result = run(['mdadm', '--detail', '--verbose', '--brief', raid_dev])
|
|
+ except (OSError, CalledProcessError) as err:
|
|
+ api.current_logger().warning(
|
|
+ 'Could not get md array component devices: {}'.format(err)
|
|
+ )
|
|
+ return None
|
|
+ # example output:
|
|
+ # ARRAY /dev/md0 level=raid1 num-devices=2 metadata=1.2 name=localhost.localdomain:0 UUID=c4acea6e:d56e1598:91822e3f:fb26832c # noqa: E501; pylint: disable=line-too-long
|
|
+ # devices=/dev/vda1,/dev/vdb1
|
|
+ if 'does not appear to be an md device' in result['stdout']:
|
|
+ raise ValueError("Expected md device, but got: {}".format(raid_dev))
|
|
+
|
|
+ return sorted(result['stdout'].rsplit('=', 2)[-1].strip().split(','))
|
|
diff --git a/repos/system_upgrade/common/libraries/tests/test_grub.py b/repos/system_upgrade/common/libraries/tests/test_grub.py
|
|
index ba086854..9ced1147 100644
|
|
--- a/repos/system_upgrade/common/libraries/tests/test_grub.py
|
|
+++ b/repos/system_upgrade/common/libraries/tests/test_grub.py
|
|
@@ -3,7 +3,7 @@ import os
|
|
import pytest
|
|
|
|
from leapp.exceptions import StopActorExecution
|
|
-from leapp.libraries.common import grub
|
|
+from leapp.libraries.common import grub, mdraid
|
|
from leapp.libraries.common.testutils import logger_mocked
|
|
from leapp.libraries.stdlib import api, CalledProcessError
|
|
from leapp.models import DefaultGrub, DefaultGrubInfo
|
|
@@ -11,6 +11,9 @@ from leapp.models import DefaultGrub, DefaultGrubInfo
|
|
BOOT_PARTITION = '/dev/vda1'
|
|
BOOT_DEVICE = '/dev/vda'
|
|
|
|
+MD_BOOT_DEVICE = '/dev/md0'
|
|
+MD_BOOT_DEVICES_WITH_GRUB = ['/dev/sda', '/dev/sdb']
|
|
+
|
|
VALID_DD = b'GRUB GeomHard DiskRead Error'
|
|
INVALID_DD = b'Nothing to see here!'
|
|
|
|
@@ -27,10 +30,11 @@ def raise_call_error(args=None):
|
|
|
|
class RunMocked(object):
|
|
|
|
- def __init__(self, raise_err=False):
|
|
+ def __init__(self, raise_err=False, boot_on_raid=False):
|
|
self.called = 0
|
|
self.args = None
|
|
self.raise_err = raise_err
|
|
+ self.boot_on_raid = boot_on_raid
|
|
|
|
def __call__(self, args, encoding=None):
|
|
self.called += 1
|
|
@@ -39,18 +43,22 @@ class RunMocked(object):
|
|
raise_call_error(args)
|
|
|
|
if self.args == ['grub2-probe', '--target=device', '/boot']:
|
|
- stdout = BOOT_PARTITION
|
|
+ stdout = MD_BOOT_DEVICE if self.boot_on_raid else BOOT_PARTITION
|
|
|
|
elif self.args == ['lsblk', '-spnlo', 'name', BOOT_PARTITION]:
|
|
stdout = BOOT_DEVICE
|
|
+ elif self.args[:-1] == ['lsblk', '-spnlo', 'name']:
|
|
+ stdout = self.args[-1][:-1]
|
|
|
|
return {'stdout': stdout}
|
|
|
|
|
|
def open_mocked(fn, flags):
|
|
- return open(
|
|
- os.path.join(CUR_DIR, 'grub_valid') if fn == BOOT_DEVICE else os.path.join(CUR_DIR, 'grub_invalid'), 'r'
|
|
- )
|
|
+ if fn == BOOT_DEVICE or fn in MD_BOOT_DEVICES_WITH_GRUB:
|
|
+ path = os.path.join(CUR_DIR, 'grub_valid')
|
|
+ else:
|
|
+ path = os.path.join(CUR_DIR, 'grub_invalid')
|
|
+ return open(path, 'r')
|
|
|
|
|
|
def open_invalid(fn, flags):
|
|
@@ -122,3 +130,54 @@ def test_is_blscfg_library(monkeypatch, enabled):
|
|
assert result
|
|
else:
|
|
assert not result
|
|
+
|
|
+
|
|
+def is_mdraid_dev_mocked(dev):
|
|
+ return dev == '/dev/md0'
|
|
+
|
|
+
|
|
+def test_get_grub_devices_one_device(monkeypatch):
|
|
+ run_mocked = RunMocked()
|
|
+ monkeypatch.setattr(grub, 'run', run_mocked)
|
|
+ monkeypatch.setattr(os, 'open', open_mocked)
|
|
+ monkeypatch.setattr(os, 'read', read_mocked)
|
|
+ monkeypatch.setattr(os, 'close', close_mocked)
|
|
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
|
|
+ monkeypatch.setattr(mdraid, 'is_mdraid_dev', is_mdraid_dev_mocked)
|
|
+
|
|
+ result = grub.get_grub_devices()
|
|
+ assert grub.run.called == 2
|
|
+ assert [BOOT_DEVICE] == result
|
|
+ assert not api.current_logger.warnmsg
|
|
+ assert 'GRUB is installed on {}'.format(",".join(result)) in api.current_logger.infomsg
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize(
|
|
+ ',component_devs,expected',
|
|
+ [
|
|
+ (['/dev/sda1', '/dev/sdb1'], MD_BOOT_DEVICES_WITH_GRUB),
|
|
+ (['/dev/sda1', '/dev/sdb1', '/dev/sdc1', '/dev/sdd1'], MD_BOOT_DEVICES_WITH_GRUB),
|
|
+ (['/dev/sda2', '/dev/sdc1'], ['/dev/sda']),
|
|
+ (['/dev/sdd3', '/dev/sdb2'], ['/dev/sdb']),
|
|
+ ]
|
|
+)
|
|
+def test_get_grub_devices_raid_device(monkeypatch, component_devs, expected):
|
|
+ run_mocked = RunMocked(boot_on_raid=True)
|
|
+ monkeypatch.setattr(grub, 'run', run_mocked)
|
|
+ monkeypatch.setattr(os, 'open', open_mocked)
|
|
+ monkeypatch.setattr(os, 'read', read_mocked)
|
|
+ monkeypatch.setattr(os, 'close', close_mocked)
|
|
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
|
|
+ monkeypatch.setattr(mdraid, 'is_mdraid_dev', is_mdraid_dev_mocked)
|
|
+
|
|
+ def get_component_devices_mocked(raid_dev):
|
|
+ assert raid_dev == MD_BOOT_DEVICE
|
|
+ return component_devs
|
|
+
|
|
+ monkeypatch.setattr(mdraid, 'get_component_devices', get_component_devices_mocked)
|
|
+
|
|
+ result = grub.get_grub_devices()
|
|
+ assert grub.run.called == 1 + len(component_devs) # grub2-probe + Nx lsblk
|
|
+ assert sorted(expected) == result
|
|
+ assert not api.current_logger.warnmsg
|
|
+ assert 'GRUB is installed on {}'.format(",".join(result)) in api.current_logger.infomsg
|
|
diff --git a/repos/system_upgrade/common/libraries/tests/test_mdraid.py b/repos/system_upgrade/common/libraries/tests/test_mdraid.py
|
|
new file mode 100644
|
|
index 00000000..6a25d736
|
|
--- /dev/null
|
|
+++ b/repos/system_upgrade/common/libraries/tests/test_mdraid.py
|
|
@@ -0,0 +1,94 @@
|
|
+import os
|
|
+
|
|
+import pytest
|
|
+
|
|
+from leapp.libraries.common import mdraid
|
|
+from leapp.libraries.common.testutils import logger_mocked
|
|
+from leapp.libraries.stdlib import api, CalledProcessError
|
|
+
|
|
+MD_DEVICE = '/dev/md0'
|
|
+NOT_MD_DEVICE = '/dev/sda'
|
|
+
|
|
+CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
+
|
|
+
|
|
+def raise_call_error(args=None):
|
|
+ raise CalledProcessError(
|
|
+ message='A Leapp Command Error occurred.',
|
|
+ command=args,
|
|
+ result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'}
|
|
+ )
|
|
+
|
|
+
|
|
+class RunMocked(object):
|
|
+
|
|
+ def __init__(self, raise_err=False):
|
|
+ self.called = 0
|
|
+ self.args = None
|
|
+ self.raise_err = raise_err
|
|
+
|
|
+ def __call__(self, args, encoding=None):
|
|
+ self.called += 1
|
|
+ self.args = args
|
|
+ if self.raise_err:
|
|
+ raise_call_error(args)
|
|
+
|
|
+ if self.args == ['mdadm', '--query', MD_DEVICE]:
|
|
+ stdout = '/dev/md0: 1022.00MiB raid1 2 devices, 0 spares. Use mdadm --detail for more detail.'
|
|
+ elif self.args == ['mdadm', '--query', NOT_MD_DEVICE]:
|
|
+ stdout = '/dev/sda: is not an md array'
|
|
+
|
|
+ elif self.args == ['mdadm', '--detail', '--verbose', '--brief', MD_DEVICE]:
|
|
+ stdout = 'ARRAY /dev/md0 level=raid1 num-devices=2 metadata=1.2 name=localhost.localdomain:0 UUID=c4acea6e:d56e1598:91822e3f:fb26832c\n devices=/dev/sda1,/dev/sdb1' # noqa: E501; pylint: disable=line-too-long
|
|
+ elif self.args == ['mdadm', '--detail', '--verbose', '--brief', NOT_MD_DEVICE]:
|
|
+ stdout = 'mdadm: /dev/sda does not appear to be an md device'
|
|
+
|
|
+ return {'stdout': stdout}
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize('dev,expected', [(MD_DEVICE, True), (NOT_MD_DEVICE, False)])
|
|
+def test_is_mdraid_dev(monkeypatch, dev, expected):
|
|
+ run_mocked = RunMocked()
|
|
+ monkeypatch.setattr(mdraid, 'run', run_mocked)
|
|
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
|
|
+
|
|
+ result = mdraid.is_mdraid_dev(dev)
|
|
+ assert mdraid.run.called == 1
|
|
+ assert expected == result
|
|
+ assert not api.current_logger.warnmsg
|
|
+
|
|
+
|
|
+def test_is_mdraid_dev_error(monkeypatch):
|
|
+ run_mocked = RunMocked(raise_err=True)
|
|
+ monkeypatch.setattr(mdraid, 'run', run_mocked)
|
|
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
|
|
+
|
|
+ with pytest.raises(CalledProcessError) as err:
|
|
+ mdraid.is_mdraid_dev(MD_DEVICE)
|
|
+
|
|
+ assert mdraid.run.called == 1
|
|
+ expect_msg = 'Could not check if device "{}" is an md device:'.format(MD_DEVICE)
|
|
+ assert expect_msg in err.value.message
|
|
+
|
|
+
|
|
+def test_get_component_devices_ok(monkeypatch):
|
|
+ run_mocked = RunMocked()
|
|
+ monkeypatch.setattr(mdraid, 'run', run_mocked)
|
|
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
|
|
+
|
|
+ result = mdraid.get_component_devices(MD_DEVICE)
|
|
+ assert mdraid.run.called == 1
|
|
+ assert ['/dev/sda1', '/dev/sdb1'] == result
|
|
+ assert not api.current_logger.warnmsg
|
|
+
|
|
+
|
|
+def test_get_component_devices_not_md_device(monkeypatch):
|
|
+ run_mocked = RunMocked()
|
|
+ monkeypatch.setattr(mdraid, 'run', run_mocked)
|
|
+
|
|
+ with pytest.raises(ValueError) as err:
|
|
+ mdraid.get_component_devices(NOT_MD_DEVICE)
|
|
+
|
|
+ assert mdraid.run.called == 1
|
|
+ expect_msg = 'Expected md device, but got: {}'.format(NOT_MD_DEVICE)
|
|
+ assert expect_msg in str(err.value)
|
|
diff --git a/repos/system_upgrade/common/models/grubinfo.py b/repos/system_upgrade/common/models/grubinfo.py
|
|
index 952d01c1..f89770b4 100644
|
|
--- a/repos/system_upgrade/common/models/grubinfo.py
|
|
+++ b/repos/system_upgrade/common/models/grubinfo.py
|
|
@@ -8,6 +8,8 @@ class GrubInfo(Model):
|
|
"""
|
|
topic = SystemFactsTopic
|
|
|
|
+ # NOTE: @deprecated is not supported on fields
|
|
+ # @deprecated(since='2023-06-23', message='This field has been replaced by orig_devices')
|
|
orig_device_name = fields.Nullable(fields.String())
|
|
"""
|
|
Original name of the block device where Grub is located.
|
|
@@ -17,3 +19,13 @@ class GrubInfo(Model):
|
|
it's recommended to use `leapp.libraries.common.grub.get_grub_device()` anywhere
|
|
else.
|
|
"""
|
|
+
|
|
+ orig_devices = fields.List(fields.String(), default=[])
|
|
+ """
|
|
+ Original names of the block devices where Grub is located.
|
|
+
|
|
+ The names are persistent during the boot of the system so it's safe to be used during
|
|
+ preupgrade phases. However the names could be different after the reboot, so
|
|
+ it's recommended to use `leapp.libraries.common.grub.get_grub_devices()` everywhere
|
|
+ else.
|
|
+ """
|
|
--
|
|
2.41.0
|
|
|