def process(): if (config.is_debug and not architecture.matches_architecture(architecture.ARCH_S390X)): # pylint: disable=using-constant-test try: # the following command prints output of grubenv for debugging purposes and is repeated after setting # default kernel so we can be sure we have the right saved entry # # saved_entry=63... # kernelopts=root=/dev/mapper... # # # boot_success and boot_indeterminate parameters are added later by one-shot systemd service stdlib.run(['grub2-editenv', 'list']) except stdlib.CalledProcessError: api.current_logger().error('Failed to execute "grub2-editenv list" command') message = next(api.consume(InstalledTargetKernelVersion), None) if not message: api.current_logger().warning(('Skipped - Forcing checking and setting default boot entry to target kernel' ' version due to missing message')) return try: current_default_kernel = stdlib.run(['grubby', '--default-kernel'])['stdout'].strip() except (OSError, stdlib.CalledProcessError): api.current_logger().warning('Failed to query grubby for default kernel', exc_info=True) return for type_ in ('index', 'title'): try: stdlib.run(['grubby', '--default-{}'.format(type_)]) except (OSError, stdlib.CalledProcessError): api.current_logger().warning('Failed to query grubby for default {}'.format(type_), exc_info=True) return kernel_info = get_kernel_info(message) if not kernel_info: return if current_default_kernel != kernel_info.kernel_path: api.current_logger().warning(('Current default boot entry not target kernel version: Current default: %s.' 'Forcing default kernel to %s'), current_default_kernel, kernel_info.kernel_path) update_default_kernel(kernel_info) if (config.is_debug and not architecture.matches_architecture(architecture.ARCH_S390X)): # pylint: disable=using-constant-test try: stdlib.run(['grub2-editenv', 'list']) except stdlib.CalledProcessError: api.current_logger().error('Failed to execute "grub2-editenv list" command')
def process(self): if not architecture.matches_architecture(architecture.ARCH_S390X): return cmdline = library.get_kernel_cmdline() if library.znet_is_set(cmdline): _report = [ reporting.Title( 'Detected the rd.znet parameter in kernel cmdline'), reporting.Severity(reporting.Severity.HIGH), reporting.Tags(([reporting.Tags.SANITY])), reporting.Flags(([reporting.Flags.INHIBITOR])), reporting.Summary( 'Upgrade on s390x machines with the rd.znet kernel' ' parameter is not supported and the upgrade has been' ' inhibited.') ] if not library.vlan_is_used(): hint = ( 'If you want to continue, remove the rd.znet parameter from' ' the kernel cmdline using grubby and zipl tools and reboot.' ' But only in case you are sure you do not the parameter' ' specified to have working network. E.g. in case you are' ' using VLAN, you should not do that.') _report.append(reporting.Remediation(hint=hint)) reporting.create_report(_report)
def process(): if not architecture.matches_architecture(architecture.ARCH_S390X): return cpuinfo = next(api.consume(CPUInfo), None) if cpuinfo is None: raise StopActorExecutionError(message=("Missing information about CPU.")) if not cpuinfo.machine_type: # this is not expected to happen, but in case... api.curernt_logger().warning("The machine (CPU) type is empty.") if cpuinfo.machine_type not in SUPPORTED_MACHINE_TYPES: summary = ("The machine is not possible to upgrade because of unsupported" " type of the processor. Regarding the official documentation," " z13 and z14 processors are supported on the Red Had Enterprise" " Linux 8 system for the IBM Z architecture. If you have one of" " the supported processors, you should see provided the machine" " type in the /proc/cpuinfo file with one of those values: {}." " Detected machine type of the CPU is '{}'." .format(", ".join([str(i) for i in SUPPORTED_MACHINE_TYPES]), cpuinfo.machine_type)) report = [ reporting.Title("The processor is not supported by the target system."), reporting.Summary(summary), reporting.Severity(reporting.Severity.HIGH), reporting.Tags([reporting.Tags.SANITY]), reporting.Flags([reporting.Flags.INHIBITOR]), reporting.ExternalLink( title="Considerations in adopting RHEL 8", url=("https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/" "html-single/considerations_in_adopting_rhel_8/" "index#changes-in-gcc-in-rhel-8_changes-in-toolchain-since-rhel-7")) ] reporting.create_report(report)
def add_boot_entry(configs=None): debug = 'debug' if os.getenv('LEAPP_DEBUG', '0') == '1' else '' kernel_dst_path, initram_dst_path = get_boot_file_paths() try: _remove_old_upgrade_boot_entry(kernel_dst_path, configs=configs) cmd = [ '/usr/sbin/grubby', '--add-kernel', '{0}'.format(kernel_dst_path), '--initrd', '{0}'.format(initram_dst_path), '--title', 'RHEL-Upgrade-Initramfs', '--copy-default', '--make-default', '--args', '{DEBUG} enforcing=0 rd.plymouth=0 plymouth.enable=0'.format( DEBUG=debug) ] if configs: for config in configs: run(cmd + ['-c', config]) else: run(cmd) if architecture.matches_architecture(architecture.ARCH_S390X): # on s390x we need to call zipl explicitly because of issue in grubby, # otherwise the new boot entry will not be set as default # See https://bugzilla.redhat.com/show_bug.cgi?id=1764306 run(['/usr/sbin/zipl']) except CalledProcessError as e: raise StopActorExecutionError( 'Cannot configure bootloader.', details={'details': '{}: {}'.format(str(e), e.stderr)})
def remove_boot_entry(): # we need to make sure /boot is mounted before trying to remove the boot entry facts_msg = api.consume(FirmwareFacts) facts = next(facts_msg, None) if not facts: raise StopActorExecutionError( 'Could not identify system firmware', details={ 'details': 'Actor did not receive FirmwareFacts message.' }) mount_points_per_firmware = { 'bios': ['/boot'], 'efi': ['/boot', '/boot/efi'] } for mp in mount_points_per_firmware[facts.firmware]: try: run(['/bin/mount', mp]) except CalledProcessError: # partitions have been most likely already mounted pass kernel_filepath = get_upgrade_kernel_filepath() run(['/usr/sbin/grubby', '--remove-kernel={0}'.format(kernel_filepath)]) if architecture.matches_architecture(architecture.ARCH_S390X): # on s390x we need to call zipl explicitly because of issue in grubby, # otherwise the new boot entry will not be set as default # See https://bugzilla.redhat.com/show_bug.cgi?id=1764306 run(['/usr/sbin/zipl']) # TODO: Move calling `mount -a` to a separate actor as it is not really related to removing the upgrade boot entry. # It's worth to call it after removing the boot entry to avoid boot loop in case mounting fails. run(['/bin/mount', '-a'])
def process(configs=None): kernel_version = next(api.consume(InstalledTargetKernelVersion), None) if kernel_version: while True: config = [] if configs: config = ['-c', configs.pop(0)] for arg in api.consume(KernelCmdlineArg): cmd = ['grubby', '--update-kernel=/boot/vmlinuz-{}'.format(kernel_version.version), '--args={}={}'.format(arg.key, arg.value)] cmd += config try: stdlib.run(cmd) if architecture.matches_architecture(architecture.ARCH_S390X): # on s390x we need to call zipl explicitly because of issue in grubby, # otherwise the entry is not updated in the ZIPL bootloader # See https://bugzilla.redhat.com/show_bug.cgi?id=1764306 stdlib.run(['/usr/sbin/zipl']) except (OSError, stdlib.CalledProcessError) as e: raise StopActorExecutionError( "Failed to append extra arguments to kernel command line.", details={"details": str(e)}) if not configs: break
def process(self): if architecture.matches_architecture(architecture.ARCH_S390X): # s390x archs use ZIPL instead of GRUB return ff = next(self.consume(FirmwareFacts), None) if ff and ff.firmware == 'bios': dev = next(self.consume(GrubDevice), None) if dev: self.produce(UpdateGrub(grub_device=dev.grub_device)) create_report([ reporting.Title( 'GRUB core will be updated during upgrade'), reporting.Summary(GRUB_SUMMARY), reporting.Severity(reporting.Severity.HIGH), reporting.Tags([reporting.Tags.BOOT]), ]) else: create_report([ reporting.Title( 'Leapp could not identify where GRUB core is located'), reporting.Summary( 'We assume GRUB core is located on the same device as /boot. Leapp needs to ' 'update GRUB core as it is not done automatically on legacy (BIOS) systems. ' ), reporting.Severity(reporting.Severity.HIGH), reporting.Tags([reporting.Tags.BOOT]), reporting.Remediation( hint= 'Please run "grub2-install <GRUB_DEVICE> command manually after upgrade' ), ])
def process(): if not architecture.matches_architecture(architecture.ARCH_S390X): return pkgs = _get_kernel_rpms() if not pkgs: # Hypothatical, user is not allowed to install any kernel that is not signed by RH # In case we would like to be cautious, we could check whether there are no other # kernels installed as well. api.current_logger().log.error( 'Cannot find any installed kernel signed by Red Hat.') raise StopActorExecutionError( 'Cannot find any installed kernel signed by Red Hat.') if len(pkgs) > 1: # It's temporary solution, so no need to try automatize everything. title = 'Multiple kernels installed' summary = ( 'The upgrade process does not handle well the case when multiple kernels' ' are installed on s390x. There is a severe risk of the bootloader configuration' ' getting corrupted during the upgrade.') remediation = ( 'Boot into the most up-to-date kernel and remove all older' ' kernels installed on the machine before running Leapp again.') reporting.create_report([ reporting.Title(title), reporting.Summary(summary), reporting.Severity(reporting.Severity.HIGH), reporting.Tags([reporting.Tags.KERNEL, reporting.Tags.BOOT]), reporting.Flags([reporting.Flags.INHIBITOR]), reporting.Remediation(hint=remediation), reporting.RelatedResource('package', 'kernel') ])
def process(): kernel_name = 'kernel' if version.is_rhel_realtime(): api.current_logger().info('The Real Time kernel boot detected.') kernel_name = 'kernel-rt' pkgs = get_pkgs(kernel_name) if not pkgs: # Hypothatical, user is not allowed to install any kernel that is not signed by RH # In case we would like to be cautious, we could check whether there are no other # kernels installed as well. api.current_logger().error( 'Cannot find any installed kernel signed by Red Hat.') raise StopActorExecutionError( 'Cannot find any installed kernel signed by Red Hat.') if len(pkgs) > 1 and architecture.matches_architecture( architecture.ARCH_S390X): # It's temporary solution, so no need to try automatize everything. title = 'Multiple kernels installed' summary = ( 'The upgrade process does not handle well the case when multiple kernels' ' are installed on s390x. There is a severe risk of the bootloader configuration' ' getting corrupted during the upgrade.') remediation = ( 'Boot into the most up-to-date kernel and remove all older' ' kernels installed on the machine before running Leapp again.') reporting.create_report([ reporting.Title(title), reporting.Summary(summary), reporting.Severity(reporting.Severity.HIGH), reporting.Tags([reporting.Tags.KERNEL, reporting.Tags.BOOT]), reporting.Flags([reporting.Flags.INHIBITOR]), reporting.Remediation(hint=remediation), reporting.RelatedResource('package', 'kernel') ]) current_evr = get_current_kernel_evr() newest_evr = get_newest_evr(pkgs) api.current_logger().debug('Current kernel EVR: {}'.format(current_evr)) api.current_logger().debug('Newest kernel EVR: {}'.format(newest_evr)) if current_evr != newest_evr: title = 'Newest installed kernel not in use' summary = ('To ensure a stable upgrade, the machine needs to be' ' booted into the latest installed kernel.') remediation = ('Boot into the most up-to-date kernel installed' ' on the machine before running Leapp again.') reporting.create_report([ reporting.Title(title), reporting.Summary(summary), reporting.Severity(reporting.Severity.HIGH), reporting.Tags([reporting.Tags.KERNEL, reporting.Tags.BOOT]), reporting.Flags([reporting.Flags.INHIBITOR]), reporting.Remediation(hint=remediation), reporting.RelatedResource('package', 'kernel') ])
def _create_initram_packages(): # copy the list as we do not want to affect the constant because of tests required_pkgs = _REQUIRED_PACKAGES[:] if architecture.matches_architecture(architecture.ARCH_X86_64): required_pkgs.append('biosdevname') if version.get_target_major_version() == '9': required_pkgs += ['policycoreutils', 'rng-tools'] return (RequiredUpgradeInitramPackages(packages=required_pkgs), TargetUserSpaceUpgradeTasks(install_rpms=required_pkgs))
def process(self): if not architecture.matches_architecture(architecture.ARCH_S390X): return userspace = next(self.consume(TargetUserSpaceInfo), None) if not userspace: # actually this should not happen, but in such case, we want to still # rather continue even if we boot into the old kernel, but in such # case, people will have to do manual actions. # NOTE: it is really just hypothetical self.log_error( 'TargetUserSpaceInfo is missing. Cannot execute zipl-switch-to-blscfg' ' to convert the zipl configuration to BLS.') raise StopActorExecutionError( 'GENERAL FAILURE: Input data for the actor are missing.') # replace the original boot directory inside the container by the host one # - as we cannot use zipl* pointing anywhere else than default directory # - no, --bls-directory is not solution with mounting.BindMount(source='/boot', target=os.path.join(userspace.path, 'boot')): userspace_zipl_conf = os.path.join(userspace.path, 'etc', 'zipl.conf') if os.path.exists(userspace_zipl_conf): os.remove(userspace_zipl_conf) with mounting.NullMount(target=userspace.path) as userspace: with userspace.nspawn() as context: context.copy_to('/etc/zipl.conf', '/etc/zipl.conf') # zipl needs this one as well context.copy_to('/etc/machine-id', '/etc/machine-id') try: context.call(['/usr/sbin/zipl-switch-to-blscfg']) if filecmp.cmp('/etc/zipl.conf', userspace_zipl_conf): # When the files are same, zipl failed - see the switch script raise OSError( 'Failed to convert the ZIPL configuration to BLS.' ) context.copy_from('/etc/zipl.conf', '/etc/zipl.conf') except OSError as e: self.log.error( 'Could not call zipl-switch-to-blscfg command.', exc_info=True) raise StopActorExecutionError( message='Failed to execute zipl-switch-to-blscfg.', details={'details': str(e)}) except CalledProcessError as e: self.log.error( 'zipl-switch-to-blscfg execution failed,', exc_info=True) raise StopActorExecutionError( message= 'zipl-switch-to-blscfg execution failed with non zero exit code.', details={ 'details': str(e), 'stdout': e.stdout, 'stderr': e.stderr })
def _check_memory(mem_info): msg = {} for arch, min_req in iter(min_req_memory.items()): if architecture.matches_architecture(arch): is_ok = mem_info.mem_total >= min_req msg = {} if is_ok else {'detected': mem_info.mem_total, 'minimal_req': min_req} return msg
def process(): if not architecture.matches_architecture(architecture.ARCH_S390X): return if os.path.isfile(DASD_CONF): # the file has to be copied into the targetuserspace container first, # then it can be included into the initramfs ==> both messages are # needed to be produced api.produce(TargetUserSpaceUpgradeTasks(copy_files=[CopyFile(src=DASD_CONF)])) api.produce(UpgradeInitramfsTasks(include_files=[DASD_CONF])) else: api.current_logger().warning( "The {} file has not been discovered. DASD not used?" .format(DASD_CONF) )
def update_default_kernel(kernel_info): try: stdlib.run(['grubby', '--info', kernel_info.kernel_path]) except stdlib.CalledProcessError: api.current_logger().error('Expected kernel %s to be installed at the boot loader but cannot be found.', kernel_info.kernel_path) except OSError: api.current_logger().error('Could not check for kernel existence in boot loader. Is grubby installed?') else: try: stdlib.run(['grubby', '--set-default', kernel_info.kernel_path]) if architecture.matches_architecture(architecture.ARCH_S390X): # on s390x we need to call zipl explicitly because of issue in grubby, # otherwise the new boot entry will not be set as default # See https://bugzilla.redhat.com/show_bug.cgi?id=1764306 stdlib.run(['/usr/sbin/zipl']) except (OSError, stdlib.CalledProcessError): api.current_logger().error('Failed to set default kernel to: %s', kernel_info.kernel_path, exc_info=True)
def _install_files(context): """ Copy the required files into the `context` userspace and return the list of those files. Those files will be copied from the source to the userspace used for generating of the upgrade initrd. These files may need to be installed in the initrd explicitly, so return the list of those files for other purposes. """ # TODO: currently we need this just for /etc/dasd.conf, but it's expected # we will need this for more files in future. The concept used here will # need to be changed, as we should consume specific messages instead. # But for now this should be enough. Keeping that for future. if architecture.matches_architecture(architecture.ARCH_S390X): # we don't need to check existence of the file - it is required on this # this architecture context.copy_to('/etc/dasd.conf', '/etc/dasd.conf') return ['/etc/dasd.conf'] return []
def perform_check(): if not architecture.matches_architecture(architecture.ARCH_S390X): return if os.path.ismount('/boot'): return data = [ reporting.Title('Leapp detected known issue related to /boot on s390x architecture'), reporting.Summary(( 'Due to a bug in the Leapp code, there is a situation when the upgrade process' ' removes content of /boot when the directory is not on a separate partition and' ' the system is running on S390x architecture. To avoid this from happening, we' ' are inhibiting the upgrade process in this release until the issue has been fixed.' )), reporting.Flags([reporting.Flags.INHIBITOR]), reporting.Tags([reporting.Tags.FILESYSTEM, reporting.Tags.UPGRADE_PROCESS, reporting.Tags.BOOT]), reporting.Severity(reporting.Severity.HIGH), ] reporting.create_report(data)
def process(self): if architecture.matches_architecture(architecture.ARCH_S390X): # For now, skip just s390x, that's only one that is failing now # because ZIPL is used there return config = '/etc/default/grub' error_detected = detect_config_error(config) if error_detected: create_report([ reporting.Title('Syntax error detected in grub configuration'), reporting.Summary( 'Syntax error was detected in GRUB_CMDLINE_LINUX value of grub configuration. ' 'This error is causing booting and other issues. ' 'Error is automatically fixed by add_upgrade_boot_entry actor.' ), reporting.Severity(reporting.Severity.LOW), reporting.Tags([reporting.Tags.BOOT]), reporting.RelatedResource('file', config) ]) self.produce(GrubConfigError(error_detected=error_detected))
def platform_check(): """ Creates an inhibitor report in case the system is not running on x86_64 """ if not architecture.matches_architecture(architecture.ARCH_X86_64): reporting.create_report([ reporting.Title( 'SAP HANA upgrades are only supported on X86_64 systems'), reporting.Summary( ('Upgrades for SAP HANA are only supported on X86_64 systems.' ' For more information please consult the documentation.')), reporting.Severity(reporting.Severity.HIGH), reporting.Tags([reporting.Tags.SANITY]), reporting.Flags([reporting.Flags.INHIBITOR]), reporting.Audience('sysadmin'), reporting.ExternalLink( url='https://access.redhat.com/solutions/5533441', title= 'How do I upgrade from Red Hat Enterprise Linux 7 to Red Hat Enterprise Linux 8 with SAP HANA' ) ]) return False return True
def process(): if not architecture.matches_architecture(architecture.ARCH_S390X): return cpuinfo = next(api.consume(CPUInfo), None) if cpuinfo is None: raise StopActorExecutionError( message=("Missing information about CPU.")) if not cpuinfo.machine_type: # this is not expected to happen, but in case... api.current_logger().warning("The machine (CPU) type is empty.") if cpuinfo.machine_type not in SUPPORTED_MACHINE_TYPES: summary = ( "The system is not possible to upgrade because of unsupported" " type of the processor. Based on the official documentation," " z14 and z15 processors are supported on the Red Hat Enterprise" " Linux 9 system for the IBM Z architecture. The supported processors" " have machine types {}. The detected machine type of the CPU is '{}'." .format(", ".join([str(i) for i in SUPPORTED_MACHINE_TYPES]), cpuinfo.machine_type)) report = [ reporting.Title( "The processor is not supported by the target system."), reporting.Summary(summary), reporting.Severity(reporting.Severity.HIGH), reporting.Tags([reporting.Tags.SANITY]), reporting.Flags([reporting.Flags.INHIBITOR]), reporting.ExternalLink( title="Considerations in adopting RHEL 8", url= ("https://access.redhat.com/ecosystem/hardware/#/search?p=1&" "c_version=Red%20Hat%20Enterprise%20Linux%208&ch_architecture=s390x" )) ] # FIXME(dhorak): update the URL to the document once it exists reporting.create_report(report)
def test_matches_architecture_fail(monkeypatch): monkeypatch.setattr(api, 'current_actor', CurrentActorMocked) assert not architecture.matches_architecture() assert not architecture.matches_architecture( *architecture.ARCH_ACCEPTED[1:])
def check_architecture(): """Check if given architecture is supported by upgrade process""" if not architecture.matches_architecture(*architecture.ARCH_SUPPORTED): inhibit_upgrade()
def test_matches_architecture_pass(monkeypatch): monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch=architecture.ARCH_ACCEPTED[0])) assert architecture.matches_architecture(*architecture.ARCH_ACCEPTED)
def test_matches_architecture_wrong_args(): with pytest.raises(TypeError): architecture.matches_architecture('aarch64', 1)
def _create_initram_packages(): required_pkgs = _REQUIRED_PACKAGES if architecture.matches_architecture(architecture.ARCH_X86_64): required_pkgs.append('biosdevname') return RequiredUpgradeInitramPackages(packages=required_pkgs)
def _create_initram_packages(): required_pkgs = _REQUIRED_PACKAGES if architecture.matches_architecture(architecture.ARCH_X86_64): required_pkgs.append('biosdevname') return (RequiredUpgradeInitramPackages(packages=required_pkgs), TargetUserSpaceUpgradeTasks(install_rpms=required_pkgs))
def process(self): if architecture.matches_architecture(architecture.ARCH_S390X): return get_grub_device()