Skip to content

Commit bfd1c3e

Browse files
committed
add handling for LVM configuration
The relevant user LVM configuration is now copied into the target userspace container along with enabling LVM dracut module for upgrade initramfs creation. The LVM configuration is copied into the target userspace container when lvm2 package in installed. Based on the configuration, the devices file is also copied in when present and enabled. The --nolvmconf option used when executing dracut is changed into --lvmconf instead if the files are copied into the target userspace container. Jira: RHEL-14712
1 parent eabab8c commit bfd1c3e

File tree

8 files changed

+220
-13
lines changed

8 files changed

+220
-13
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from leapp.actors import Actor
2+
from leapp.libraries.actor.checklvm import check_lvm
3+
from leapp.models import DistributionSignedRPM, LVMConfig, TargetUserSpaceUpgradeTasks, UpgradeInitramfsTasks
4+
from leapp.reporting import Report
5+
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
6+
7+
8+
class CheckLVM(Actor):
9+
"""
10+
Check if the LVM is installed and ensure the target userspace container
11+
and initramfs are prepared to support it.
12+
13+
The LVM configuration files are copied into the target userspace container
14+
so that the dracut is able to use them while creating the initramfs.
15+
The dracut LVM module is enabled by this actor as well.
16+
"""
17+
18+
name = 'check_lvm'
19+
consumes = (DistributionSignedRPM, LVMConfig)
20+
produces = (Report, TargetUserSpaceUpgradeTasks, UpgradeInitramfsTasks)
21+
tags = (ChecksPhaseTag, IPUWorkflowTag)
22+
23+
def process(self):
24+
check_lvm()
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import os
2+
3+
from leapp import reporting
4+
from leapp.libraries.common.rpms import has_package
5+
from leapp.libraries.stdlib import api
6+
from leapp.models import (
7+
CopyFile,
8+
DistributionSignedRPM,
9+
DracutModule,
10+
LVMConfig,
11+
TargetUserSpaceUpgradeTasks,
12+
UpgradeInitramfsTasks
13+
)
14+
15+
LVM_CONFIG_PATH = '/etc/lvm/lvm.conf'
16+
LVM_DEVICES_FILE_PATH_PREFIX = '/etc/lvm/devices'
17+
18+
19+
def _report_filter_detection():
20+
title = 'LVM filter definition detected.'
21+
summary = (
22+
'RHEL 9 and above uses the LVM devices file by default to select devices used by LVM. '
23+
f'Since this system has LVM filter defined in the {LVM_CONFIG_PATH}, it will be '
24+
'used after the upgrade as well.'
25+
)
26+
27+
remediation_hint = (
28+
'While not mandatory, switching to the LVM devices file from the LVM filter is possible '
29+
'using the following command. It uses the existing LVM filter to create the system.devices '
30+
'file which is then used instead of the LVM filter as long as it exists. Before running the command, '
31+
'make sure that the use_devicesfile=1 (default for RHEL 9 and above).'
32+
)
33+
remediation_command = ['vgimportdevices', '-a']
34+
35+
reporting.create_report([
36+
reporting.Title(title),
37+
reporting.Summary(summary),
38+
reporting.Remediation(hint=remediation_hint, commands=[remediation_command]),
39+
reporting.ExternalLink(
40+
title='Limiting LVM device visibility and usage',
41+
url='https://red.ht/3MfgK7c',
42+
),
43+
reporting.Severity(reporting.Severity.INFO),
44+
])
45+
46+
47+
def check_lvm():
48+
if not has_package(DistributionSignedRPM, 'lvm2'):
49+
return
50+
51+
lvm_config = next(api.consume(LVMConfig), None)
52+
if not lvm_config:
53+
return
54+
55+
lvm_devices_file_path = os.path.join(LVM_DEVICES_FILE_PATH_PREFIX, lvm_config.devices.devicesfile)
56+
lvm_devices_file_exists = os.path.isfile(lvm_devices_file_path)
57+
58+
filters_used = not lvm_config.devices.use_devicesfile or not lvm_devices_file_exists
59+
if filters_used:
60+
_report_filter_detection()
61+
62+
api.current_logger().debug('Including lvm dracut module.')
63+
api.produce(UpgradeInitramfsTasks(include_dracut_modules=[DracutModule(name='lvm')]))
64+
# TODO: decide if we need to install lvm2 package in the container as well
65+
66+
copy_files = []
67+
api.current_logger().debug('Copying "{}" to the target userspace.'.format(LVM_CONFIG_PATH))
68+
copy_files.append(CopyFile(src=LVM_CONFIG_PATH))
69+
70+
if lvm_devices_file_exists and lvm_config.devices.use_devicesfile:
71+
api.current_logger().debug('Copying "{}" to the target userspace.'.format(lvm_devices_file_path))
72+
copy_files.append(CopyFile(src=lvm_devices_file_path))
73+
74+
api.produce(TargetUserSpaceUpgradeTasks(copy_files=copy_files))

repos/system_upgrade/common/actors/checklvm/tests/test_checklvm.py

Whitespace-only changes.

repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
BootContent,
77
FIPSInfo,
88
LiveModeConfig,
9+
LVMConfig,
910
TargetOSInstallationImage,
1011
TargetUserSpaceInfo,
1112
TargetUserSpaceUpgradeTasks,
@@ -31,6 +32,7 @@ class UpgradeInitramfsGenerator(Actor):
3132
consumes = (
3233
FIPSInfo,
3334
LiveModeConfig,
35+
LVMConfig,
3436
RequiredUpgradeInitramPackages, # deprecated
3537
TargetOSInstallationImage,
3638
TargetUserSpaceInfo,

repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from leapp.models import (
1313
BootContent,
1414
LiveModeConfig,
15+
LVMConfig,
1516
TargetOSInstallationImage,
1617
TargetUserSpaceInfo,
1718
TargetUserSpaceUpgradeTasks,
@@ -363,20 +364,29 @@ def generate_initram_disk(context):
363364
def fmt_module_list(module_list):
364365
return ','.join(mod.name for mod in module_list)
365366

367+
env_variables = [
368+
'LEAPP_KERNEL_VERSION={kernel_version}',
369+
'LEAPP_ADD_DRACUT_MODULES="{dracut_modules}"',
370+
'LEAPP_KERNEL_ARCH={arch}',
371+
'LEAPP_ADD_KERNEL_MODULES="{kernel_modules}"',
372+
'LEAPP_DRACUT_INSTALL_FILES="{files}"'
373+
]
374+
375+
if next(api.consume(LVMConfig), None):
376+
env_variables.append('LEAPP_DRACUT_LVMCONF="1"')
377+
378+
env_variables = ' '.join(env_variables)
379+
env_variables = env_variables.format(
380+
kernel_version=_get_target_kernel_version(context),
381+
dracut_modules=fmt_module_list(initramfs_includes.dracut_modules),
382+
kernel_modules=fmt_module_list(initramfs_includes.kernel_modules),
383+
arch=api.current_actor().configuration.architecture,
384+
files=' '.join(initramfs_includes.files)
385+
)
386+
cmd = os.path.join('/', INITRAM_GEN_SCRIPT_NAME)
387+
366388
# FIXME: issue #376
367-
context.call([
368-
'/bin/sh', '-c',
369-
'LEAPP_KERNEL_VERSION={kernel_version} '
370-
'LEAPP_ADD_DRACUT_MODULES="{dracut_modules}" LEAPP_KERNEL_ARCH={arch} '
371-
'LEAPP_ADD_KERNEL_MODULES="{kernel_modules}" '
372-
'LEAPP_DRACUT_INSTALL_FILES="{files}" {cmd}'.format(
373-
kernel_version=_get_target_kernel_version(context),
374-
dracut_modules=fmt_module_list(initramfs_includes.dracut_modules),
375-
kernel_modules=fmt_module_list(initramfs_includes.kernel_modules),
376-
arch=api.current_actor().configuration.architecture,
377-
files=' '.join(initramfs_includes.files),
378-
cmd=os.path.join('/', INITRAM_GEN_SCRIPT_NAME))
379-
], env=env)
389+
context.call(['/bin/sh', '-c', f'{env_variables} {cmd}'], env=env)
380390

381391
boot_files_info = copy_boot_files(context)
382392
return boot_files_info
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from leapp.actors import Actor
2+
from leapp.libraries.actor import scanner
3+
from leapp.models import DistributionSignedRPM, LVMConfig
4+
from leapp.tags import FactsPhaseTag, IPUWorkflowTag
5+
6+
7+
class ScanLVMConfig(Actor):
8+
"""
9+
Scan LVM configuration.
10+
"""
11+
12+
name = 'scan_lvm_config'
13+
consumes = (DistributionSignedRPM,)
14+
produces = (LVMConfig,)
15+
tags = (FactsPhaseTag, IPUWorkflowTag)
16+
17+
def process(self):
18+
scanner.scan()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import os
2+
import re
3+
4+
from leapp.libraries.common.config.version import get_source_major_version
5+
from leapp.libraries.common.rpms import has_package
6+
from leapp.libraries.stdlib import api
7+
from leapp.models import DistributionSignedRPM, LVMConfig, LVMConfigDevicesSection
8+
9+
LVM_CONFIG_PATH = '/etc/lvm/lvm.conf'
10+
11+
12+
def _lvm_config_devices_parser(lvm_config_lines):
13+
in_section = False
14+
config = {}
15+
for line in lvm_config_lines:
16+
line = line.split("#", 1)[0].strip()
17+
if not line:
18+
continue
19+
if "devices {" in line:
20+
in_section = True
21+
continue
22+
if in_section and "}" in line:
23+
in_section = False
24+
if in_section:
25+
value = line.split("=", 1)
26+
config[value[0].strip()] = value[1].strip().strip('"')
27+
return config
28+
29+
30+
def _read_config_lines(path):
31+
with open(path) as lvm_conf_file:
32+
return lvm_conf_file.readlines()
33+
34+
35+
def scan():
36+
if not has_package(DistributionSignedRPM, 'lvm2'):
37+
return
38+
39+
if not os.path.isfile(LVM_CONFIG_PATH):
40+
api.current_logger().debug('The "{}" is not present on the system.'.format(LVM_CONFIG_PATH))
41+
return
42+
43+
lvm_config_lines = _read_config_lines(LVM_CONFIG_PATH)
44+
devices_section = _lvm_config_devices_parser(lvm_config_lines)
45+
46+
lvm_config_devices = LVMConfigDevicesSection(use_devicesfile=int(get_source_major_version()) > 8)
47+
if 'devicesfile' in devices_section:
48+
lvm_config_devices.devicesfile = devices_section['devicesfile']
49+
50+
if 'use_devicesfile' in devices_section and devices_section['use_devicesfile'] in ['0', '1']:
51+
lvm_config_devices.use_devicesfile = devices_section['use_devicesfile'] == '1'
52+
53+
api.produce(LVMConfig(devices=lvm_config_devices))
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from leapp.models import fields, Model
2+
from leapp.topics import SystemInfoTopic
3+
4+
5+
class LVMConfigDevicesSection(Model):
6+
"""The devices section from the LVM configuration."""
7+
topic = SystemInfoTopic
8+
9+
use_devicesfile = fields.Boolean()
10+
"""
11+
Determines whether only the devices in the devices file are used by LVM. Note
12+
that the default value changed on the RHEL 9 to True.
13+
"""
14+
15+
devicesfile = fields.String(default="system.devices")
16+
"""
17+
Defines the name of the devices file that should be used. The default devices
18+
file is located in '/etc/lvm/devices/system.devices'.
19+
"""
20+
21+
22+
class LVMConfig(Model):
23+
"""LVM configuration split into sections."""
24+
topic = SystemInfoTopic
25+
26+
devices = fields.Model(LVMConfigDevicesSection)

0 commit comments

Comments
 (0)