Skip to content

Commit c5accf4

Browse files
dkubekpirat89
authored andcommitted
Add inhibitor for unsupported XFS
Kernel in RHEL 10 drops support of XFS v4 format file systems and such file systems will not be possible to mount there anymore. Also, RHEL 10 systems will be challenged by Y2K38 problem. XFS file system resolves that by `bigtime` feature, however, this feature could be manually disabled when creating the file system and mainly it's available since RHEL 9. So all XFS file systems created earlier will not have this enabled - unless users do it on RHEL 9 manually. Note that this will be problem for all systems with XFS which upgraded from previous RHEL versions. For this reason, inhibit the upgrade if any mounted XFS file systems have old XFS v4 format (detect `crc=0`). Instead of inhibiting upgrade when the `bigtime` feature is disabled (`bigtime=0` or missing) a low severity report is created as there is still time until this issue will be present and other solutions are being worked on. Introduces new model `XFSInfoFacts` which collects parsed information about all mounted XFS file systems. Note that as we use only a few values from `xfs_info` utility, models specify now just this limited amount of values as well to limit the burden of maintanance and risk of issues. However expected design of the model is already prepared and other expected fields are commented out in the code to make the further extension in future more simple for others. All values of XFS attributes are now represented as strings. JIRA: RHELMISC-8212, RHEL-60034, RHEL-52309
1 parent 0a5f66e commit c5accf4

File tree

7 files changed

+1027
-167
lines changed

7 files changed

+1027
-167
lines changed

Diff for: repos/system_upgrade/common/actors/xfsinfoscanner/actor.py

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
11
from leapp.actors import Actor
22
from leapp.libraries.actor.xfsinfoscanner import scan_xfs
3-
from leapp.models import StorageInfo, XFSPresence
3+
from leapp.models import StorageInfo, XFSInfoFacts, XFSPresence
44
from leapp.tags import FactsPhaseTag, IPUWorkflowTag
55

66

77
class XFSInfoScanner(Actor):
88
"""
9-
This actor scans all mounted mountpoints for XFS information
9+
This actor scans all mounted mountpoints for XFS information.
10+
11+
The actor checks the `StorageInfo` message, which contains details about
12+
the system's storage. For each mountpoint reported, it determines whether
13+
the filesystem is XFS and collects information about its configuration.
14+
Specifically, it identifies whether the XFS filesystem is using `ftype=0`,
15+
which requires special handling for overlay filesystems.
16+
17+
The actor produces two types of messages:
18+
19+
- `XFSPresence`: Indicates whether any XFS use `ftype=0`, and lists the
20+
mountpoints where `ftype=0` is used.
21+
22+
- `XFSInfoFacts`: Contains detailed metadata about all XFS mountpoints.
23+
This includes sections parsed from the `xfs_info` command.
1024
11-
The actor will check each mountpoint reported in the StorageInfo message, if the mountpoint is a partition with XFS
12-
using ftype = 0. The actor will produce a message with the findings.
13-
It will contain a list of all XFS mountpoints with ftype = 0 so that those mountpoints can be handled appropriately
14-
for the overlayfs that is going to be created.
1525
"""
1626

1727
name = 'xfs_info_scanner'
1828
consumes = (StorageInfo,)
19-
produces = (XFSPresence,)
29+
produces = (XFSPresence, XFSInfoFacts,)
2030
tags = (FactsPhaseTag, IPUWorkflowTag,)
2131

2232
def process(self):
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,74 @@
11
import os
2+
import re
23

34
from leapp.libraries.stdlib import api, CalledProcessError, run
4-
from leapp.models import StorageInfo, XFSPresence
5+
from leapp.models import (
6+
StorageInfo,
7+
XFSInfo,
8+
XFSInfoData,
9+
XFSInfoFacts,
10+
XFSInfoLog,
11+
XFSInfoMetaData,
12+
XFSInfoNaming,
13+
XFSInfoRealtime,
14+
XFSPresence
15+
)
16+
17+
18+
def scan_xfs():
19+
storage_info_msgs = api.consume(StorageInfo)
20+
storage_info = next(storage_info_msgs, None)
21+
22+
if list(storage_info_msgs):
23+
api.current_logger().warning(
24+
'Unexpectedly received more than one StorageInfo message.'
25+
)
26+
27+
fstab_data = set()
28+
mount_data = set()
29+
if storage_info:
30+
fstab_data = scan_xfs_fstab(storage_info.fstab)
31+
mount_data = scan_xfs_mount(storage_info.mount)
32+
33+
mountpoints = fstab_data | mount_data
34+
35+
xfs_infos = {}
36+
for mountpoint in mountpoints:
37+
content = read_xfs_info(mountpoint)
38+
if content is None:
39+
continue
40+
41+
xfs_info = parse_xfs_info(content)
42+
xfs_infos[mountpoint] = xfs_info
43+
44+
mountpoints_ftype0 = [
45+
mountpoint
46+
for mountpoint in xfs_infos
47+
if is_without_ftype(xfs_infos[mountpoint])
48+
]
49+
50+
# By now, we only have XFS mountpoints and check whether or not it has
51+
# ftype = 0
52+
api.produce(XFSPresence(
53+
present=len(mountpoints) > 0,
54+
without_ftype=len(mountpoints_ftype0) > 0,
55+
mountpoints_without_ftype=mountpoints_ftype0,
56+
))
57+
58+
api.produce(
59+
XFSInfoFacts(
60+
mountpoints=[
61+
generate_xfsinfo_for_mountpoint(xfs_infos[mountpoint], mountpoint)
62+
for mountpoint in xfs_infos
63+
]
64+
)
65+
)
566

667

768
def scan_xfs_fstab(data):
869
mountpoints = set()
970
for entry in data:
10-
if entry.fs_vfstype == "xfs":
71+
if entry.fs_vfstype == 'xfs':
1172
mountpoints.add(entry.fs_file)
1273

1374
return mountpoints
@@ -16,49 +77,116 @@ def scan_xfs_fstab(data):
1677
def scan_xfs_mount(data):
1778
mountpoints = set()
1879
for entry in data:
19-
if entry.tp == "xfs":
80+
if entry.tp == 'xfs':
2081
mountpoints.add(entry.mount)
2182

2283
return mountpoints
2384

2485

25-
def is_xfs_without_ftype(mp):
26-
if not os.path.ismount(mp):
27-
# Check if mp is actually a mountpoint
28-
api.current_logger().warning('{} is not mounted'.format(mp))
29-
return False
86+
def read_xfs_info(mp):
87+
if not is_mountpoint(mp):
88+
return None
89+
3090
try:
31-
xfs_info = run(['/usr/sbin/xfs_info', '{}'.format(mp)], split=True)
91+
result = run(['/usr/sbin/xfs_info', '{}'.format(mp)], split=True)
3292
except CalledProcessError as err:
33-
api.current_logger().warning('Error during command execution: {}'.format(err))
34-
return False
35-
36-
for l in xfs_info['stdout']:
37-
if 'ftype=0' in l:
38-
return True
39-
40-
return False
41-
93+
api.current_logger().warning(
94+
'Error during command execution: {}'.format(err)
95+
)
96+
return None
4297

43-
def scan_xfs():
44-
storage_info_msgs = api.consume(StorageInfo)
45-
storage_info = next(storage_info_msgs, None)
98+
return result['stdout']
4699

47-
if list(storage_info_msgs):
48-
api.current_logger().warning('Unexpectedly received more than one StorageInfo message.')
49100

50-
fstab_data = set()
51-
mount_data = set()
52-
if storage_info:
53-
fstab_data = scan_xfs_fstab(storage_info.fstab)
54-
mount_data = scan_xfs_mount(storage_info.mount)
55-
56-
mountpoints = fstab_data | mount_data
57-
mountpoints_ftype0 = list(filter(is_xfs_without_ftype, mountpoints))
101+
def is_mountpoint(mp):
102+
if not os.path.ismount(mp):
103+
# Check if mp is actually a mountpoint
104+
api.current_logger().warning('{} is not mounted'.format(mp))
105+
return False
58106

59-
# By now, we only have XFS mountpoints and check whether or not it has ftype = 0
60-
api.produce(XFSPresence(
61-
present=len(mountpoints) > 0,
62-
without_ftype=len(mountpoints_ftype0) > 0,
63-
mountpoints_without_ftype=mountpoints_ftype0,
64-
))
107+
return True
108+
109+
110+
def parse_xfs_info(content):
111+
"""
112+
This parser reads the output of the ``xfs_info`` command.
113+
114+
In general the pattern is::
115+
116+
section =sectionkey key1=value1 key2=value2, key3=value3
117+
= key4=value4
118+
nextsec =sectionkey sectionvalue key=value otherkey=othervalue
119+
120+
Sections are continued over lines as per RFC822. The first equals
121+
sign is column-aligned, and the first key=value is too, but the
122+
rest seems to be comma separated. Specifiers come after the first
123+
equals sign, and sometimes have a value property, but sometimes not.
124+
125+
NOTE: This function is adapted from [1]
126+
127+
[1]: https://github.com/RedHatInsights/insights-core/blob/master/insights/parsers/xfs_info.py
128+
"""
129+
130+
xfs_info = {}
131+
132+
info_re = re.compile(r'^(?P<section>[\w-]+)?\s*' +
133+
r'=(?:(?P<specifier>\S+)(?:\s(?P<specval>\w+))?)?' +
134+
r'\s+(?P<keyvaldata>\w.*\w)$'
135+
)
136+
keyval_re = re.compile(r'(?P<key>[\w-]+)=(?P<value>\d+(?: blks)?)')
137+
138+
sect_info = None
139+
140+
for line in content:
141+
match = info_re.search(line)
142+
if match:
143+
if match.group('section'):
144+
# Change of section - make new sect_info dict and link
145+
sect_info = {}
146+
xfs_info[match.group('section')] = sect_info
147+
if match.group('specifier'):
148+
sect_info['specifier'] = match.group('specifier')
149+
if match.group('specval'):
150+
sect_info['specifier_value'] = match.group('specval')
151+
for key, value in keyval_re.findall(match.group('keyvaldata')):
152+
sect_info[key] = value
153+
154+
# Normalize strings
155+
xfs_info = {
156+
str(section): {
157+
str(attr): str(value)
158+
for attr, value in sect_info.items()
159+
}
160+
for section, sect_info in xfs_info.items()
161+
}
162+
163+
return xfs_info
164+
165+
166+
def is_without_ftype(xfs_info):
167+
return xfs_info['naming'].get('ftype', '') == '0'
168+
169+
170+
def generate_xfsinfo_for_mountpoint(xfs_info, mountpoint):
171+
result = XFSInfo(
172+
mountpoint=mountpoint,
173+
meta_data=XFSInfoMetaData(
174+
device=xfs_info['meta-data']['specifier'],
175+
bigtime=xfs_info['meta-data'].get('bigtime'),
176+
crc=xfs_info['meta-data'].get('crc'),
177+
),
178+
data=XFSInfoData(
179+
bsize=xfs_info['data']['bsize'],
180+
blocks=xfs_info['data']['blocks']
181+
),
182+
naming=XFSInfoNaming(
183+
ftype=xfs_info['naming']['ftype']
184+
),
185+
log=XFSInfoLog(
186+
bsize=xfs_info['log']['bsize'],
187+
blocks=xfs_info['log']['blocks']
188+
),
189+
realtime=XFSInfoRealtime(),
190+
)
191+
192+
return result

0 commit comments

Comments
 (0)