Skip to content

Commit

Permalink
Add XFS inhibitor
Browse files Browse the repository at this point in the history
  • Loading branch information
dkubek committed Dec 9, 2024
1 parent 3c3421a commit 9e5f33e
Show file tree
Hide file tree
Showing 7 changed files with 569 additions and 125 deletions.
7 changes: 5 additions & 2 deletions repos/system_upgrade/common/actors/xfsinfoscanner/actor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from leapp.actors import Actor
from leapp.libraries.actor.xfsinfoscanner import scan_xfs
from leapp.models import StorageInfo, XFSPresence
from leapp.models import StorageInfo, XFSPresence, XFSInfoFacts
from leapp.tags import FactsPhaseTag, IPUWorkflowTag


Expand All @@ -12,11 +12,14 @@ class XFSInfoScanner(Actor):
using ftype = 0. The actor will produce a message with the findings.
It will contain a list of all XFS mountpoints with ftype = 0 so that those mountpoints can be handled appropriately
for the overlayfs that is going to be created.
"""

#FIXME: add XFSInfoFacts to description

name = 'xfs_info_scanner'
consumes = (StorageInfo,)
produces = (XFSPresence,)
produces = (XFSPresence, XFSInfoFacts,)
tags = (FactsPhaseTag, IPUWorkflowTag,)

def process(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,63 @@
import re
import os

from leapp.libraries.stdlib import api, CalledProcessError, run
from leapp.models import StorageInfo, XFSPresence
from leapp.models import StorageInfo, XFSPresence, XFSInfo, XFSInfoFacts
from leapp.exceptions import StopActorExecutionError


def scan_xfs():
storage_info_msgs = api.consume(StorageInfo)
storage_info = next(storage_info_msgs, None)

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

fstab_data = set()
mount_data = set()
if storage_info:
fstab_data = scan_xfs_fstab(storage_info.fstab)
mount_data = scan_xfs_mount(storage_info.mount)

mountpoints = fstab_data | mount_data

xfs_infos = {}
for mountpoint in mountpoints:
content = read_xfs_info(mountpoint)
if content is None:
continue

xfs_info = parse_xfs_info(content)
xfs_infos[mountpoint] = xfs_info

mountpoints_ftype0 = [
mountpoint
for mountpoint in xfs_infos
if is_without_ftype(xfs_infos[mountpoint])
]

# By now, we only have XFS mountpoints and check whether or not it has ftype = 0
api.produce(XFSPresence(
present=len(mountpoints) > 0,
without_ftype=len(mountpoints_ftype0) > 0,
mountpoints_without_ftype=mountpoints_ftype0,
))

api.produce(
XFSInfoFacts(
mountpoints=[
XFSInfo(
mountpoint=mountpoint,
meta_data=xfs_infos[mountpoint]['meta-data'],
data=xfs_infos[mountpoint]['data'],
naming=xfs_infos[mountpoint]['naming'],
log=xfs_infos[mountpoint]['log'],
realtime=xfs_infos[mountpoint]['realtime'],
)
for mountpoint in xfs_infos
]
)
)


def scan_xfs_fstab(data):
Expand All @@ -22,43 +78,94 @@ def scan_xfs_mount(data):
return mountpoints


def is_xfs_without_ftype(mp):
if not os.path.ismount(mp):
# Check if mp is actually a mountpoint
api.current_logger().warning('{} is not mounted'.format(mp))
return False
def read_xfs_info(mp):
if not is_mountpoint(mp):
return None

try:
xfs_info = run(['/usr/sbin/xfs_info', '{}'.format(mp)], split=True)
result = run(['/usr/sbin/xfs_info', '{}'.format(mp)], split=True)
except CalledProcessError as err:
api.current_logger().warning('Error during command execution: {}'.format(err))
return False
return None

for l in xfs_info['stdout']:
if 'ftype=0' in l:
return True
return result['stdout']

return False

def is_mountpoint(mp):
if not os.path.ismount(mp):
# Check if mp is actually a mountpoint
api.current_logger().warning('{} is not mounted'.format(mp))
return False

def scan_xfs():
storage_info_msgs = api.consume(StorageInfo)
storage_info = next(storage_info_msgs, None)

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

fstab_data = set()
mount_data = set()
if storage_info:
fstab_data = scan_xfs_fstab(storage_info.fstab)
mount_data = scan_xfs_mount(storage_info.mount)

mountpoints = fstab_data | mount_data
mountpoints_ftype0 = list(filter(is_xfs_without_ftype, mountpoints))

# By now, we only have XFS mountpoints and check whether or not it has ftype = 0
api.produce(XFSPresence(
present=len(mountpoints) > 0,
without_ftype=len(mountpoints_ftype0) > 0,
mountpoints_without_ftype=mountpoints_ftype0,
))
return True


def parse_xfs_info(content):
"""
This parser reads the output of the ``xfs_info`` command.
In general the pattern is::
section =sectionkey key1=value1 key2=value2, key3=value3
= key4=value4
nextsec =sectionkey sectionvalue key=value otherkey=othervalue
Sections are continued over lines as per RFC822. The first equals
sign is column-aligned, and the first key=value is too, but the
rest seems to be comma separated. Specifiers come after the first
equals sign, and sometimes have a value property, but sometimes not.
NOTE: This function is adapted from
https://github.com/RedHatInsights/insights-core/blob/5118165b22a983a6b9c6d90f85cdedaece3f05f9/insights/parsers/xfs_info.py#L126
"""

xfs_info = {}

info_re = re.compile(r'^(?P<section>[\w-]+)?\s*' +
r'=(?:(?P<specifier>\S+)(?:\s(?P<specval>\w+))?)?' +
r'\s+(?P<keyvaldata>\w.*\w)$'
)
keyval_re = re.compile(r'(?P<key>[\w-]+)=(?P<value>\d+(?: blks)?)')

sect_info = None

for line in content:
match = info_re.search(line)
if match:
if match.group('section'):
# Change of section - make new sect_info dict and link
sect_info = {}
xfs_info[match.group('section')] = sect_info
if match.group('specifier'):
sect_info['specifier'] = match.group('specifier')
if match.group('specval'):
sect_info['specifier_value'] = match.group('specval')
for key, value in keyval_re.findall(match.group('keyvaldata')):
sect_info[key] = value

_validate_xfs_info(xfs_info)

return xfs_info


def _validate_xfs_info(xfs_info):
if 'meta-data' not in xfs_info:
raise StopActorExecutionError("No 'meta-data' section found")
if 'specifier' not in xfs_info['meta-data']:
raise StopActorExecutionError("Device specifier not found in meta-data")
if 'data' not in xfs_info:
raise StopActorExecutionError("No 'data' section found")
if 'blocks' not in xfs_info['data']:
raise StopActorExecutionError("'blocks' not defined in data section")
if 'bsize' not in xfs_info['data']:
raise StopActorExecutionError("'bsize' not defined in data section")
if 'log' not in xfs_info:
raise StopActorExecutionError("No 'log' section found")
if 'blocks' not in xfs_info['log']:
raise StopActorExecutionError("'blocks' not defined in log section")
if 'bsize' not in xfs_info['log']:
raise StopActorExecutionError("'bsize' not defined in log section")


def is_without_ftype(xfs_info):
return xfs_info['naming'].get('ftype', '') == '0'
Loading

0 comments on commit 9e5f33e

Please sign in to comment.