leapp-repository/0025-Inhibit-unsupported-x86-64-microarchitecture-RHEL9.patch
Petr Stodulka c64266d19b Pre CTC2 candidate
- Introduce new upgrade path RHEL 8.9 -> 9.3
- Update leapp data files to reflect new changes between systems
- Detect and report use of deprecated Xorg drivers
- Minor improvements of generated reports
- Fix false positive report about invalid symlinks
- Inhibit the upgrade when unsupported x86-64 microarchitecture is detected
- Resolves: rhbz#2215997
2023-06-19 23:53:40 +02:00

378 lines
17 KiB
Diff

From 777e0a641739add1fca50af774d6d924af5550d7 Mon Sep 17 00:00:00 2001
From: David Kubek <dkubek@redhat.com>
Date: Tue, 14 Mar 2023 11:54:18 +0100
Subject: [PATCH 25/30] Inhibit unsupported x86-64 microarchitecture RHEL9
As per [x86-64-ABI][1] In addition to the AMD64 baseline architecture,
several micro-architecture levels implemented by later CPU modules have
been defined, starting at level ``x86-64-v2``.
RHEL9 has a higher CPU requirement than older versions, it now requires
a CPU compatible with ``x86-64-v2`` instruction set or higher. Until
now, there was no check for this and the upgrade crashed unexpectedly.
This commit handles this issue and provides the user with a report
explaining the problem.
The CPU Features are gathered using the ``lscpu`` command by way of
using the ``ScanCPU`` actor. The ``ScanCPU`` actor had to be also
modified to provide the required flags. The mapping of CPU Features to
flags provided by ``lscpu`` has been determined by using the
``/arch/x86/include/asm/cpufeatures.h`` file from the linux kernel.
[1]: https://gitlab.com/x86-psABIs/x86-64-ABI.git
---
.../actors/scancpu/libraries/scancpu.py | 13 +++-
.../actors/scancpu/tests/test_scancpu.py | 74 +++++++++++++++----
repos/system_upgrade/common/models/cpuinfo.py | 4 +-
.../actors/checkmicroarchitecture/actor.py | 63 ++++++++++++++++
.../libraries/checkmicroarchitecture.py | 47 ++++++++++++
.../tests/test_checkmicroarchitecture.py | 65 ++++++++++++++++
6 files changed, 249 insertions(+), 17 deletions(-)
create mode 100644 repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/actor.py
create mode 100644 repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/libraries/checkmicroarchitecture.py
create mode 100644 repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/tests/test_checkmicroarchitecture.py
diff --git a/repos/system_upgrade/common/actors/scancpu/libraries/scancpu.py b/repos/system_upgrade/common/actors/scancpu/libraries/scancpu.py
index 68f5623b..e5555f99 100644
--- a/repos/system_upgrade/common/actors/scancpu/libraries/scancpu.py
+++ b/repos/system_upgrade/common/actors/scancpu/libraries/scancpu.py
@@ -17,6 +17,11 @@ def _get_lscpu_output():
return ''
+def _get_cpu_flags(lscpu):
+ flags = lscpu.get('Flags', '')
+ return flags.split()
+
+
def _get_cpu_entries_for(arch_prefix):
result = []
for message in api.consume(DeviceDriverDeprecationData):
@@ -137,4 +142,10 @@ def process():
api.produce(*_find_deprecation_data_entries(lscpu))
# Backwards compatibility
machine_type = lscpu.get('Machine type')
- api.produce(CPUInfo(machine_type=int(machine_type) if machine_type else None))
+ flags = _get_cpu_flags(lscpu)
+ api.produce(
+ CPUInfo(
+ machine_type=int(machine_type) if machine_type else None,
+ flags=flags
+ )
+ )
diff --git a/repos/system_upgrade/common/actors/scancpu/tests/test_scancpu.py b/repos/system_upgrade/common/actors/scancpu/tests/test_scancpu.py
index 44d4de87..894fae08 100644
--- a/repos/system_upgrade/common/actors/scancpu/tests/test_scancpu.py
+++ b/repos/system_upgrade/common/actors/scancpu/tests/test_scancpu.py
@@ -1,14 +1,59 @@
import os
+import pytest
+
from leapp.libraries.actor import scancpu
from leapp.libraries.common import testutils
+from leapp.libraries.common.config.architecture import (
+ ARCH_ARM64,
+ ARCH_PPC64LE,
+ ARCH_S390X,
+ ARCH_SUPPORTED,
+ ARCH_X86_64
+)
from leapp.libraries.stdlib import api
from leapp.models import CPUInfo
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
+LSCPU = {
+ ARCH_ARM64: {
+ "machine_type": None,
+ "flags": ['fp', 'asimd', 'evtstrm', 'aes', 'pmull', 'sha1', 'sha2', 'crc32', 'cpuid'],
+ },
+ ARCH_PPC64LE: {
+ "machine_type": None,
+ "flags": []
+ },
+ ARCH_S390X: {
+ "machine_type":
+ 2827,
+ "flags": [
+ 'esan3', 'zarch', 'stfle', 'msa', 'ldisp', 'eimm', 'dfp', 'edat', 'etf3eh', 'highgprs', 'te', 'vx', 'vxd',
+ 'vxe', 'gs', 'vxe2', 'vxp', 'sort', 'dflt', 'sie'
+ ]
+ },
+ ARCH_X86_64: {
+ "machine_type":
+ None,
+ "flags": [
+ 'fpu', 'vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce', 'cx8', 'apic', 'sep', 'mtrr', 'pge', 'mca', 'cmov',
+ 'pat', 'pse36', 'clflush', 'dts', 'acpi', 'mmx', 'fxsr', 'sse', 'sse2', 'ss', 'ht', 'tm', 'pbe', 'syscall',
+ 'nx', 'pdpe1gb', 'rdtscp', 'lm', 'constant_tsc', 'arch_perfmon', 'pebs', 'bts', 'rep_good', 'nopl',
+ 'xtopology', 'nonstop_tsc', 'cpuid', 'aperfmperf', 'pni', 'pclmulqdq', 'dtes64', 'monitor', 'ds_cpl',
+ 'vmx', 'smx', 'est', 'tm2', 'ssse3', 'sdbg', 'fma', 'cx16', 'xtpr', 'pdcm', 'pcid', 'dca', 'sse4_1',
+ 'sse4_2', 'x2apic', 'movbe', 'popcnt', 'tsc_deadline_timer', 'aes', 'xsave', 'avx', 'f16c', 'rdrand',
+ 'lahf_lm', 'abm', 'cpuid_fault', 'epb', 'invpcid_single', 'pti', 'ssbd', 'ibrs', 'ibpb', 'stibp',
+ 'tpr_shadow', 'vnmi', 'flexpriority', 'ept', 'vpid', 'ept_ad', 'fsgsbase', 'tsc_adjust', 'bmi1', 'avx2',
+ 'smep', 'bmi2', 'erms', 'invpcid', 'cqm', 'xsaveopt', 'cqm_llc', 'cqm_occup_llc', 'dtherm', 'ida', 'arat',
+ 'pln', 'pts', 'md_clear', 'flush_l1d'
+ ]
+ },
+}
+
class mocked_get_cpuinfo(object):
+
def __init__(self, filename):
self.filename = filename
@@ -22,24 +67,25 @@ class mocked_get_cpuinfo(object):
return '\n'.join(fp.read().splitlines())
-def test_machine_type(monkeypatch):
+@pytest.mark.parametrize("arch", ARCH_SUPPORTED)
+def test_scancpu(monkeypatch, arch):
- # cpuinfo doesn't contain a machine field
- mocked_cpuinfo = mocked_get_cpuinfo('lscpu_x86_64')
+ mocked_cpuinfo = mocked_get_cpuinfo('lscpu_' + arch)
monkeypatch.setattr(scancpu, '_get_lscpu_output', mocked_cpuinfo)
monkeypatch.setattr(api, 'produce', testutils.produce_mocked())
- current_actor = testutils.CurrentActorMocked(arch=testutils.architecture.ARCH_X86_64)
+ current_actor = testutils.CurrentActorMocked(arch=arch)
monkeypatch.setattr(api, 'current_actor', current_actor)
- scancpu.process()
- assert api.produce.called == 1
- assert CPUInfo() == api.produce.model_instances[0]
- # cpuinfo contains a machine field
- api.produce.called = 0
- api.produce.model_instances = []
- current_actor = testutils.CurrentActorMocked(arch=testutils.architecture.ARCH_S390X)
- monkeypatch.setattr(api, 'current_actor', current_actor)
- mocked_cpuinfo.filename = 'lscpu_s390x'
scancpu.process()
+
+ expected = CPUInfo(machine_type=LSCPU[arch]["machine_type"], flags=LSCPU[arch]["flags"])
+ produced = api.produce.model_instances[0]
+
assert api.produce.called == 1
- assert CPUInfo(machine_type=2827) == api.produce.model_instances[0]
+
+ # Produced what was expected
+ assert expected.machine_type == produced.machine_type
+ assert sorted(expected.flags) == sorted(produced.flags)
+
+ # Did not produce anything extra
+ assert expected == produced
diff --git a/repos/system_upgrade/common/models/cpuinfo.py b/repos/system_upgrade/common/models/cpuinfo.py
index e3e52838..ee245563 100644
--- a/repos/system_upgrade/common/models/cpuinfo.py
+++ b/repos/system_upgrade/common/models/cpuinfo.py
@@ -32,8 +32,8 @@ class CPUInfo(Model):
# byte_order = fields.StringEnum(['Little Endian', 'Big Endian'])
# """ Byte order of the CPU: 'Little Endian' or 'Big Endian' """
- # flags = fields.List(fields.String(), default=[])
- # """ Specifies flags/features of the CPU. """
+ flags = fields.List(fields.String(), default=[])
+ """ Specifies flags/features of the CPU. """
# hypervisor = fields.Nullable(fields.String())
# hypervisor_vendor = fields.Nullable(fields.String())
diff --git a/repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/actor.py b/repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/actor.py
new file mode 100644
index 00000000..98ffea80
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/actor.py
@@ -0,0 +1,63 @@
+import leapp.libraries.actor.checkmicroarchitecture as checkmicroarchitecture
+from leapp.actors import Actor
+from leapp.models import CPUInfo
+from leapp.reporting import Report
+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
+
+
+class CheckMicroarchitecture(Actor):
+ """
+ Inhibit if RHEL9 microarchitecture requirements are not satisfied
+
+
+ As per `x86-64-ABI`_ In addition to the AMD64 baseline architecture, several
+ micro-architecture levels implemented by later CPU modules have been
+ defined, starting at level ``x86-64-v2``. The levels are cumulative in the
+ sense that features from previous levels are implicitly included in later
+ levels.
+
+ RHEL9 has a higher CPU requirement than older versions, it now requires a
+ CPU compatible with ``x86-64-v2`` instruction set or higher.
+
+ .. table:: Required CPU features by microarchitecure level with a
+ corresponding flag as shown by ``lscpu``.
+
+ +------------+-------------+--------------------+
+ | Version | CPU Feature | flag (lscpu) |
+ +============+=============+====================+
+ | (baseline) | CMOV | cmov |
+ | | CX8 | cx8 |
+ | | FPU | fpu |
+ | | FXSR | fxsr |
+ | | MMX | mmx |
+ | | OSFXSR | (common with FXSR) |
+ | | SCE | syscall |
+ | | SSE | sse |
+ | | SSE2 | sse2 |
+ +------------+-------------+--------------------+
+ | x86-64-v2 | CMPXCHG16B | cx16 |
+ | | LAHF-SAHF | lahf_lm |
+ | | POPCNT | popcnt |
+ | | SSE3 | pni |
+ | | SSE4_1 | sse4_1 |
+ | | SSE4_2 | sse4_2 |
+ | | SSSE3 | ssse3 |
+ +------------+-------------+--------------------+
+ | ... | | |
+ +------------+-------------+--------------------+
+
+ Note: To get the corresponding flag for the CPU feature consult the file
+ ``/arch/x86/include/asm/cpufeatures.h`` in the linux kernel.
+
+
+ .. _x86-64-ABI: https://gitlab.com/x86-psABIs/x86-64-ABI.git
+
+ """
+
+ name = 'check_microarchitecture'
+ consumes = (CPUInfo,)
+ produces = (Report,)
+ tags = (ChecksPhaseTag, IPUWorkflowTag,)
+
+ def process(self):
+ checkmicroarchitecture.process()
diff --git a/repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/libraries/checkmicroarchitecture.py b/repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/libraries/checkmicroarchitecture.py
new file mode 100644
index 00000000..0f1f1fca
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/libraries/checkmicroarchitecture.py
@@ -0,0 +1,47 @@
+from leapp import reporting
+from leapp.libraries.common.config.architecture import ARCH_X86_64, matches_architecture
+from leapp.libraries.stdlib import api
+from leapp.models import CPUInfo
+
+X86_64_BASELINE_FLAGS = ['cmov', 'cx8', 'fpu', 'fxsr', 'mmx', 'syscall', 'sse', 'sse2']
+X86_64_V2_FLAGS = ['cx16', 'lahf_lm', 'popcnt', 'pni', 'sse4_1', 'sse4_2', 'ssse3']
+
+
+def _inhibit_upgrade(missing_flags):
+ title = 'Current x86-64 microarchitecture is unsupported in RHEL9'
+ summary = ('RHEL9 has a higher CPU requirement than older versions, it now requires a CPU '
+ 'compatible with x86-64-v2 instruction set or higher.\n\n'
+ 'Missings flags detected are: {}\n'.format(', '.join(missing_flags)))
+
+ reporting.create_report([
+ reporting.Title(title),
+ reporting.Summary(summary),
+ reporting.ExternalLink(title='Building Red Hat Enterprise Linux 9 for the x86-64-v2 microarchitecture level',
+ url=('https://developers.redhat.com/blog/2021/01/05/'
+ 'building-red-hat-enterprise-linux-9-for-the-x86-64-v2-microarchitecture-level')),
+ reporting.Severity(reporting.Severity.HIGH),
+ reporting.Groups([reporting.Groups.INHIBITOR]),
+ reporting.Groups([reporting.Groups.SANITY]),
+ reporting.Remediation(hint=('If case of using virtualization, virtualization platforms often allow '
+ 'configuring a minimum denominator CPU model for compatibility when migrating '
+ 'between different CPU models. Ensure that minimum requirements are not below '
+ 'that of RHEL9\n')),
+ ])
+
+
+def process():
+ """
+ Check whether the processor matches the required microarchitecture.
+ """
+
+ if not matches_architecture(ARCH_X86_64):
+ api.current_logger().info('Architecture not x86-64. Skipping microarchitecture test.')
+ return
+
+ cpuinfo = next(api.consume(CPUInfo))
+
+ required_flags = X86_64_BASELINE_FLAGS + X86_64_V2_FLAGS
+ missing_flags = [flag for flag in required_flags if flag not in cpuinfo.flags]
+ api.current_logger().debug('Required flags missing: %s', missing_flags)
+ if missing_flags:
+ _inhibit_upgrade(missing_flags)
diff --git a/repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/tests/test_checkmicroarchitecture.py b/repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/tests/test_checkmicroarchitecture.py
new file mode 100644
index 00000000..b7c850d9
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/checkmicroarchitecture/tests/test_checkmicroarchitecture.py
@@ -0,0 +1,65 @@
+import pytest
+
+from leapp import reporting
+from leapp.libraries.actor import checkmicroarchitecture
+from leapp.libraries.common.config.architecture import ARCH_SUPPORTED, ARCH_X86_64
+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, logger_mocked
+from leapp.libraries.stdlib import api
+from leapp.models import CPUInfo
+from leapp.utils.report import is_inhibitor
+
+
+@pytest.mark.parametrize("arch", [arch for arch in ARCH_SUPPORTED if not arch == ARCH_X86_64])
+def test_not_x86_64_passes(monkeypatch, arch):
+ """
+ Test no report is generated on an architecture different from x86-64
+ """
+
+ monkeypatch.setattr(reporting, "create_report", create_report_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch=arch))
+
+ checkmicroarchitecture.process()
+
+ assert 'Architecture not x86-64. Skipping microarchitecture test.' in api.current_logger.infomsg
+ assert not reporting.create_report.called
+
+
+def test_valid_microarchitecture(monkeypatch):
+ """
+ Test no report is generated on a valid microarchitecture
+ """
+
+ monkeypatch.setattr(reporting, "create_report", create_report_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+
+ required_flags = checkmicroarchitecture.X86_64_BASELINE_FLAGS + checkmicroarchitecture.X86_64_V2_FLAGS
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch=ARCH_X86_64,
+ msgs=[CPUInfo(flags=required_flags)]))
+
+ checkmicroarchitecture.process()
+
+ assert 'Architecture not x86-64. Skipping microarchitecture test.' not in api.current_logger.infomsg
+ assert not reporting.create_report.called
+
+
+def test_invalid_microarchitecture(monkeypatch):
+ """
+ Test report is generated on x86-64 architecture with invalid microarchitecture and the upgrade is inhibited
+ """
+
+ monkeypatch.setattr(reporting, "create_report", create_report_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch=ARCH_X86_64, msgs=[CPUInfo()]))
+
+ checkmicroarchitecture.process()
+
+ produced_title = reporting.create_report.report_fields.get('title')
+ produced_summary = reporting.create_report.report_fields.get('summary')
+
+ assert 'Architecture not x86-64. Skipping microarchitecture test.' not in api.current_logger().infomsg
+ assert reporting.create_report.called == 1
+ assert 'microarchitecture is unsupported' in produced_title
+ assert 'RHEL9 has a higher CPU requirement' in produced_summary
+ assert reporting.create_report.report_fields['severity'] == reporting.Severity.HIGH
+ assert is_inhibitor(reporting.create_report.report_fields)
--
2.40.1