From c4dd2d39ef6465bff0d56806e2bffd6b4bbaefae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Zdraveck=C3=BD?= Date: Tue, 31 Oct 2023 14:48:09 +0100 Subject: [PATCH 1/5] leapp upgrade automation script init --- scripts/leapp_upgrade.py | 378 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 scripts/leapp_upgrade.py diff --git a/scripts/leapp_upgrade.py b/scripts/leapp_upgrade.py new file mode 100644 index 0000000..60e7a8a --- /dev/null +++ b/scripts/leapp_upgrade.py @@ -0,0 +1,378 @@ +import json +import os +import subprocess + + +JSON_REPORT_PATH = "/var/log/leapp/leapp-report.json" +TXT_REPORT_PATH = "/var/log/leapp/leapp-report.txt" + +# Based on https://github.com/oamg/leapp/blob/master/report-schema-v110.json#L211 +STATUS_CODE = { + "high": 3, + "medium": 2, + "low": 1, + "info": 0, +} +STATUS_CODE_NAME_MAP = { + "high": "ERROR", + "medium": "WARNING", + "low": "WARNING", + "info": "INFO", +} + + +# Both classes taken from: +# https://github.com/oamg/convert2rhel-worker-scripts/blob/main/scripts/preconversion_assessment_script.py +class ProcessError(Exception): + """Custom exception to report errors during setup and run of leapp""" + + def __init__(self, message): + super(ProcessError, self).__init__(message) + self.message = message + + +class OutputCollector(object): + """Wrapper class for script expected stdout""" + + def __init__(self, status="", message="", report="", entries=None): + self.status = status + self.message = message + self.report = report + self.tasks_format_version = "1.0" + self.tasks_format_id = "oamg-format" + self.entries = entries + self.report_json = None + + def to_dict(self): + # If we have entries, then we change report_json to be a dictionary + # with the needed values, otherwise, we leave it as `None` to be + # transformed to `null` in json. + if self.entries: + self.report_json = { + "tasks_format_version": self.tasks_format_version, + "tasks_format_id": self.tasks_format_id, + "entries": self.entries, + } + + return { + "status": self.status, + "message": self.message, + "report": self.report, + "report_json": self.report_json, + } + + +def get_rhel_version(): + """Currently we execute the task only for RHEL 7 or 8""" + print("Checking OS distribution and version ID ...") + try: + distribution_id = None + version_id = None + with open("/etc/os-release", "r") as os_release_file: + for line in os_release_file: + if line.startswith("ID="): + distribution_id = line.split("=")[1].strip().strip('"') + elif line.startswith("VERSION_ID="): + version_id = line.split("=")[1].strip().strip('"') + except IOError: + print("Couldn't read /etc/os-release") + return distribution_id, version_id + + +def is_non_eligible_releases(release): + print("Exit if not RHEL 7.9") # TODO: fill with current data, idk what is the current version + major_version, minor = release.split(".") if release is not None else (None, None) + return release is None or major_version != "7" or minor != "9" + + +# Code taken from +# https://github.com/oamg/convert2rhel/blob/v1.4.1/convert2rhel/utils.py#L345 +# and modified to adapt the needs of the tools that are being executed in this +# script. +def run_subprocess(cmd, print_cmd=True, env=None, wait=True): + """ + Call the passed command and optionally log the called command + (print_cmd=True) and environment variables in form of dictionary(env=None). + Switching off printing the command can be useful in case it contains a + password in plain text. + + The cmd is specified as a list starting with the command and followed by a + list of arguments. Example: ["yum", "install", ""] + """ + if isinstance(cmd, str): + raise TypeError("cmd should be a list, not a str") + + if print_cmd: + print("Calling command '%s'" % " ".join(cmd)) + + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, env=env + ) + + output = "" + + if not wait: + return output, None + + for line in iter(process.stdout.readline, b""): + line = line.decode("utf8") + output += line + + # Call wait() to wait for the process to terminate so that we can + # get the return code. + process.wait() + + return output, process.returncode + + +def check_if_package_installed(pkg_name): + _, return_code = run_subprocess(["rpm", "-q", pkg_name]) + return return_code == 0 + + +def setup_leapp(command, rhui_pkgs): + print("Installing leapp ...") + output, returncode = run_subprocess(command) + if returncode: + print( + "Installation of leapp failed with code '%s' and output: %s\n" + % (returncode, output) + ) + raise ProcessError( + message="Installation of leapp failed with code '%s'." % returncode + ) + + print("Check installed rhui packages ...") + for pkg in rhui_pkgs: + if check_if_package_installed(pkg["src_pkg"]): + pkg["installed"] = True + return [pkg for pkg in rhui_pkgs if pkg.get("installed", False)] + + +def should_use_no_rhsm_check(rhui_installed, command): + print("Checking if subscription manager and repositories are available ...") + rhsm_repo_check_fail = True + _, rhsm_installed_check = run_subprocess(["which", "subscription-manager"]) + if rhsm_installed_check == 0: + rhsm_repo_check, _ = run_subprocess( + ["subscription-manager", "repos", "--list-enabled"] + ) + rhsm_repo_check_fail = ( + "This system has no repositories available through subscriptions." + in rhsm_repo_check + or "Repositories disabled by configuration." in rhsm_repo_check + ) + + if rhui_installed and not rhsm_repo_check_fail: + print("RHUI packages detected, adding --no-rhsm flag to preupgrade command") + command.append("--no-rhsm") + return True + return False + + +def install_leapp_pkg_corresponding_to_installed_rhui(rhui_pkgs): + print("Installing leapp package corresponding to installed rhui packages") + for pkg in rhui_pkgs: + install_pkg = pkg["leapp_pkg"] + install_output, returncode = run_subprocess( + ["yum", "install", "-y", install_pkg] + ) + if returncode: + print("Installation of %s failed. \n%s" % (install_pkg, install_output)) + raise ProcessError( + message="Installation of %s (coresponding pkg to '%s') failed with exit code %s." + % (install_pkg, pkg, returncode) + ) + + +def remove_previous_reports(): + print("Removing previous preupgrade reports at /var/log/leapp/leapp-report.* ...") + + if os.path.exists(JSON_REPORT_PATH): + os.remove(JSON_REPORT_PATH) + + if os.path.exists(TXT_REPORT_PATH): + os.remove(TXT_REPORT_PATH) + + +def execute_upgrade(command): + print("Executing upgrade ...") + _, _ = run_subprocess(command) + + # NOTE: we do not care about returncode because non-null always means actor error (or leapp error) + # if returncode: + # print( + # "The process leapp exited with code '%s' and output: %s\n" + # % (returncode, output) + # ) + # raise ProcessError(message="Leapp exited with code '%s'." % returncode) + + +def find_highest_report_level(entries): + """ + Gather status codes from entries. + """ + print("Collecting and combining report status.") + action_level_combined = [] + for value in entries: + action_level_combined.append(value["severity"]) + + valid_action_levels = [ + level for level in action_level_combined if level in STATUS_CODE + ] + valid_action_levels.sort(key=lambda status: STATUS_CODE[status], reverse=True) + return STATUS_CODE_NAME_MAP[valid_action_levels[0]] + + +def parse_results(output): + print("Processing upgrade results ...") + + report_json = "Not found" + message = "Can't open json report at " + JSON_REPORT_PATH + alert = True + status = "ERROR" + + print("Reading JSON report") + if os.path.exists(JSON_REPORT_PATH): + with open(JSON_REPORT_PATH, mode="r") as handler: + report_json = json.load(handler) + + # NOTE: with newer schema we will need to parse groups instead of flags + report_entries = report_json.get("entries", []) + inhibitor_count = len( + [entry for entry in report_entries if "inhibitor" in entry.get("flags")] + ) + message = "Your system has %s inhibitors out of %s potential problems." % ( + inhibitor_count, + len(report_entries), + ) + alert = inhibitor_count > 0 + status = ( + find_highest_report_level(report_entries) + if len(report_entries) > 0 + else "SUCCESS" + ) + + output.status = status + output.report_json = report_json + output.alert = alert + output.message = message + + print("Reading TXT report") + report_txt = "Not found" + if os.path.exists(TXT_REPORT_PATH): + with open(JSON_REPORT_PATH, mode="r") as handler: + report_txt = handler.read() + + output.report = report_txt + + +def call_insights_client(): + print("Calling insight-client in background for immediate data collection.") + run_subprocess(["insights-client"], wait=False) + # NOTE: we do not care about returncode or output because we are not waiting for process to finish + + +def main(): + # Exit if not RHEL 7 or 8 + dist, version = get_rhel_version() + if dist != "rhel" or is_non_eligible_releases(version): + raise ProcessError( + message='Exiting because distribution="%s" and version="%s"' + % (dist, version) + ) + + output = OutputCollector() + + try: + # Init variables + upgrade_command = ["/usr/bin/leapp", "upgrade"] + use_no_rhsm = False + rhui_pkgs = [] + if version.startswith("7"): + leapp_install_command = [ + "yum", + "install", + "leapp-upgrade", + "-y", + "--enablerepo=rhel-7-server-extras-rpms", + ] + rhel_7_rhui_packages = [ + {"src_pkg": "rh-amazon-rhui-client", "leapp_pkg": "leapp-rhui-aws"}, + { + "src_pkg": "rh-amazon-rhui-client-sap-bundle", + "leapp_pkg": "leapp-rhui-aws-sap-e4s", + }, + {"src_pkg": "rhui-azure-rhel7", "leapp_pkg": "leapp-rhui-azure"}, + { + "src_pkg": "rhui-azure-rhel7-base-sap-apps", + "leapp_pkg": "leapp-rhui-azure-sap", + }, + { + "src_pkg": "rhui-azure-rhel7-base-sap-ha", + "leapp_pkg": "leapp-rhui-azure-sap", + }, + { + "src_pkg": "google-rhui-client-rhel7", + "leapp_pkg": "leapp-rhui-google", + }, + { + "src_pkg": "google-rhui-client-rhel79-sap", + "leapp_pkg": "leapp-rhui-google-sap", + }, + ] + rhui_pkgs = setup_leapp(leapp_install_command, rhel_7_rhui_packages) + if version.startswith("8"): + leapp_install_command = ["dnf", "install", "leapp-upgrade", "-y"] + rhel_8_rhui_packages = [ + {"src_pkg": "rh-amazon-rhui-client", "leapp_pkg": "leapp-rhui-aws"}, + { + "src_pkg": "rh-amazon-rhui-client-sap-bundle-e4s", + "leapp_pkg": "leapp-rhui-aws-sap-e4s", + }, + {"src_pkg": "rhui-azure-rhel8", "leapp_pkg": "leapp-rhui-azure"}, + { + "src_pkg": "rhui-azure-rhel8-eus", + "leapp_pkg": "leapp-rhui-azure-eus", + }, + { + "src_pkg": "rhui-azure-rhel8-sap-ha", + "leapp_pkg": "leapp-rhui-azure-sap", + }, + { + "src_pkg": "rhui-azure-rhel8-sapapps", + "leapp_pkg": "leapp-rhui-azure-sap", + }, + { + "src_pkg": "google-rhui-client-rhel8", + "leapp_pkg": "leapp-rhui-google", + }, + { + "src_pkg": "google-rhui-client-rhel8-sap", + "leapp_pkg": "leapp-rhui-google-sap", + }, + ] + rhui_pkgs = setup_leapp(leapp_install_command, rhel_8_rhui_packages) + + use_no_rhsm = should_use_no_rhsm_check(len(rhui_pkgs) > 1, upgrade_command) + + if use_no_rhsm: + install_leapp_pkg_corresponding_to_installed_rhui(rhui_pkgs) + + remove_previous_reports() + execute_upgrade(upgrade_command) + print("Upgrade successfully executed.") + parse_results(output) + except ProcessError as exception: + output = OutputCollector(status="ERROR", report=exception.message) + except Exception as exception: + output = OutputCollector(status="ERROR", report=str(exception)) + finally: + print("### JSON START ###") + print(json.dumps(output.to_dict(), indent=4)) + print("### JSON END ###") + call_insights_client() + + +if __name__ == "__main__": + main() From 165edf94856d6a52fb75de5a3a0b6fa7af1c5dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Zdraveck=C3=BD?= Date: Fri, 10 Nov 2023 18:53:22 +0100 Subject: [PATCH 2/5] Update preupgrade script and add tests --- README.md | 9 +- scripts/leapp_upgrade.py | 30 +++--- tests/upgrade/test_check_pkg_installed.py | 13 +++ tests/upgrade/test_collect_report_level.py | 57 +++++++++++ tests/upgrade/test_get_rhel_version.py | 20 ++++ tests/upgrade/test_insights_client_call.py | 9 ++ tests/upgrade/test_install_rhui_pkgs.py | 38 +++++++ tests/upgrade/test_main.py | 113 +++++++++++++++++++++ tests/upgrade/test_non_eligible_release.py | 12 +++ tests/upgrade/test_parse_results.py | 41 ++++++++ tests/upgrade/test_reboot.py | 9 ++ tests/upgrade/test_remove_reports.py | 23 +++++ tests/upgrade/test_run.py | 10 ++ tests/upgrade/test_run_subprocess.py | 97 ++++++++++++++++++ tests/upgrade/test_schema.py | 32 ++++++ tests/upgrade/test_setup_leapp.py | 36 +++++++ tests/upgrade/test_should_use_rhsm.py | 50 +++++++++ 17 files changed, 583 insertions(+), 16 deletions(-) create mode 100644 tests/upgrade/test_check_pkg_installed.py create mode 100644 tests/upgrade/test_collect_report_level.py create mode 100644 tests/upgrade/test_get_rhel_version.py create mode 100644 tests/upgrade/test_insights_client_call.py create mode 100644 tests/upgrade/test_install_rhui_pkgs.py create mode 100644 tests/upgrade/test_main.py create mode 100644 tests/upgrade/test_non_eligible_release.py create mode 100644 tests/upgrade/test_parse_results.py create mode 100644 tests/upgrade/test_reboot.py create mode 100644 tests/upgrade/test_remove_reports.py create mode 100644 tests/upgrade/test_run.py create mode 100644 tests/upgrade/test_run_subprocess.py create mode 100644 tests/upgrade/test_schema.py create mode 100644 tests/upgrade/test_setup_leapp.py create mode 100644 tests/upgrade/test_should_use_rhsm.py diff --git a/README.md b/README.md index 411be25..4c1ff8e 100644 --- a/README.md +++ b/README.md @@ -43,15 +43,18 @@ You can find official leapp_preupgrade_ansible.yaml for rhc-worker-playbook in p For rhc-worker-script we have only raw script leapp_preupgrade.py, signed version wrapped in yaml is still in development. -### [WIP] Upgrade +### Upgrade -TBA +**TBD** for rhc-worker-playbook. + +For rhc-worker-script we have only raw script leapp_upgrade.py, signed version wrapped in yaml is still in development. ## Local Development & Contributing ### Requirements -* `virtualenv` - to run tests locally +* `python2` - to run tests locally +* `virtualenv` - `versions < 20.22.0` to run tests locally * `pre-commit` - to run checks before each commit, see hook in [.pre-commit-config.yml](./.pre-commit-config.yaml) * `make` - to use handy commands diff --git a/scripts/leapp_upgrade.py b/scripts/leapp_upgrade.py index 60e7a8a..b2c47f2 100644 --- a/scripts/leapp_upgrade.py +++ b/scripts/leapp_upgrade.py @@ -5,6 +5,7 @@ JSON_REPORT_PATH = "/var/log/leapp/leapp-report.json" TXT_REPORT_PATH = "/var/log/leapp/leapp-report.txt" +REBOOT_GUIDANCE_MESSAGE = "A reboot is required to continue. Please reboot your system." # Based on https://github.com/oamg/leapp/blob/master/report-schema-v110.json#L211 STATUS_CODE = { @@ -80,9 +81,11 @@ def get_rhel_version(): def is_non_eligible_releases(release): - print("Exit if not RHEL 7.9") # TODO: fill with current data, idk what is the current version + print("Exit if not RHEL 7.9 or 8.4") major_version, minor = release.split(".") if release is not None else (None, None) - return release is None or major_version != "7" or minor != "9" + version_str = major_version + "." + minor + eligible_releases = ["7.9", "8.4"] + return release is None or version_str not in eligible_releases # Code taken from @@ -197,15 +200,9 @@ def remove_previous_reports(): def execute_upgrade(command): print("Executing upgrade ...") - _, _ = run_subprocess(command) + output, _ = run_subprocess(command) - # NOTE: we do not care about returncode because non-null always means actor error (or leapp error) - # if returncode: - # print( - # "The process leapp exited with code '%s' and output: %s\n" - # % (returncode, output) - # ) - # raise ProcessError(message="Leapp exited with code '%s'." % returncode) + return output def find_highest_report_level(entries): @@ -273,8 +270,12 @@ def call_insights_client(): # NOTE: we do not care about returncode or output because we are not waiting for process to finish +def reboot_system(): + print("Rebooting system in 1 minute.") + run_subprocess(["shutdown", "-r", "1"], wait=False) + + def main(): - # Exit if not RHEL 7 or 8 dist, version = get_rhel_version() if dist != "rhel" or is_non_eligible_releases(version): raise ProcessError( @@ -283,7 +284,7 @@ def main(): ) output = OutputCollector() - + leapp_upgrade_output = None try: # Init variables upgrade_command = ["/usr/bin/leapp", "upgrade"] @@ -360,7 +361,7 @@ def main(): install_leapp_pkg_corresponding_to_installed_rhui(rhui_pkgs) remove_previous_reports() - execute_upgrade(upgrade_command) + leapp_upgrade_output = execute_upgrade(upgrade_command) print("Upgrade successfully executed.") parse_results(output) except ProcessError as exception: @@ -373,6 +374,9 @@ def main(): print("### JSON END ###") call_insights_client() + if leapp_upgrade_output and REBOOT_GUIDANCE_MESSAGE in leapp_upgrade_output: + reboot_system() + if __name__ == "__main__": main() diff --git a/tests/upgrade/test_check_pkg_installed.py b/tests/upgrade/test_check_pkg_installed.py new file mode 100644 index 0000000..4dc162b --- /dev/null +++ b/tests/upgrade/test_check_pkg_installed.py @@ -0,0 +1,13 @@ +from mock import patch + +from scripts.leapp_upgrade import check_if_package_installed + + +@patch("scripts.leapp_upgrade.run_subprocess") +def test_check_if_package_installed(mock_run_subprocess): + pkg_name = "example-package" + mock_run_subprocess.return_value = ("", 0) + result = check_if_package_installed(pkg_name) + expected_command = ["rpm", "-q", pkg_name] + mock_run_subprocess.assert_called_once_with(expected_command) + assert result diff --git a/tests/upgrade/test_collect_report_level.py b/tests/upgrade/test_collect_report_level.py new file mode 100644 index 0000000..54d64fa --- /dev/null +++ b/tests/upgrade/test_collect_report_level.py @@ -0,0 +1,57 @@ +import pytest +from scripts.leapp_upgrade import ( + find_highest_report_level, +) + + +@pytest.mark.parametrize( + ("entries", "expected"), + ( + ( + [ + { + "severity": "high", + }, + { + "severity": "info", + }, + ], + "ERROR", + ), + ( + [ + { + "severity": "info", + } + ], + "INFO", + ), + ( + [ + { + "severity": "medium", + }, + { + "severity": "info", + }, + ], + "WARNING", + ), + ), +) +def test_find_highest_report_level_expected(entries, expected): + """Should be sorted descending from the highest status to the lower one.""" + result = find_highest_report_level(entries) + assert result == expected + + +def test_find_highest_report_level_unknown_status(): + """Should ignore unknown statuses in report""" + expected_output = "WARNING" + + action_results_test = [ + {"severity": "medium"}, + {"severity": "foo"}, + ] + result = find_highest_report_level(action_results_test) + assert result == expected_output diff --git a/tests/upgrade/test_get_rhel_version.py b/tests/upgrade/test_get_rhel_version.py new file mode 100644 index 0000000..8abf1ab --- /dev/null +++ b/tests/upgrade/test_get_rhel_version.py @@ -0,0 +1,20 @@ +from mock import patch, mock_open +from scripts.leapp_upgrade import get_rhel_version + + +@patch("__builtin__.open", mock_open(read_data='ID="rhel"\nVERSION_ID="7.9"\n')) +def test_get_rhel_version_with_existing_file(): + distribution_id, version_id = get_rhel_version() + + assert distribution_id == "rhel" + assert version_id == "7.9" + + +@patch("__builtin__.open") +def test_get_rhel_version_with_missing_file(mock_open_file): + mock_open_file.side_effect = IOError("Couldn't read /etc/os-release") + + distribution_id, version_id = get_rhel_version() + + assert distribution_id is None + assert version_id is None diff --git a/tests/upgrade/test_insights_client_call.py b/tests/upgrade/test_insights_client_call.py new file mode 100644 index 0000000..9ca4909 --- /dev/null +++ b/tests/upgrade/test_insights_client_call.py @@ -0,0 +1,9 @@ +from mock import patch + +from scripts.leapp_upgrade import call_insights_client + + +@patch("scripts.leapp_upgrade.run_subprocess", return_value=(b"", 0)) +def test_call_insights_client(mock_popen): + call_insights_client() + mock_popen.assert_called_once_with(["insights-client"], wait=False) diff --git a/tests/upgrade/test_install_rhui_pkgs.py b/tests/upgrade/test_install_rhui_pkgs.py new file mode 100644 index 0000000..5a101d9 --- /dev/null +++ b/tests/upgrade/test_install_rhui_pkgs.py @@ -0,0 +1,38 @@ +import pytest +from mock import patch, call +from scripts.leapp_upgrade import ( + ProcessError, + install_leapp_pkg_corresponding_to_installed_rhui, +) + + +@patch("scripts.leapp_upgrade.run_subprocess") +def test_install_leapp_pkg_to_installed_rhui(mock_run_subprocess): + rhui_pkgs = [{"leapp_pkg": "pkg1"}, {"leapp_pkg": "pkg2"}] + + mock_run_subprocess.return_value = ("Installation successful", 0) + + install_leapp_pkg_corresponding_to_installed_rhui(rhui_pkgs) + + for pkg in rhui_pkgs: + expected_command = ["yum", "install", "-y", pkg["leapp_pkg"]] + assert call(expected_command) in mock_run_subprocess.call_args_list + + +@patch("scripts.leapp_upgrade.run_subprocess") +def test_install_leapp_pkge_to_installed_rhui_error( + mock_run_subprocess, +): + rhui_pkgs = [{"leapp_pkg": "pkg1"}, {"leapp_pkg": "pkg2"}] + mock_run_subprocess.return_value = ("Installation failed", 1) + + with pytest.raises(ProcessError) as exception: + install_leapp_pkg_corresponding_to_installed_rhui(rhui_pkgs) + + expected_command = ["yum", "install", "-y", "pkg1"] + mock_run_subprocess.assert_called_once_with(expected_command) + + assert ( + str(exception.value) + == "Installation of pkg1 (coresponding pkg to '{'leapp_pkg': 'pkg1'}') failed with exit code 1." + ) diff --git a/tests/upgrade/test_main.py b/tests/upgrade/test_main.py new file mode 100644 index 0000000..9fbbcd4 --- /dev/null +++ b/tests/upgrade/test_main.py @@ -0,0 +1,113 @@ +import pytest +from mock import patch +from scripts.leapp_upgrade import ( + main, + ProcessError, + OutputCollector, + REBOOT_GUIDANCE_MESSAGE, +) + + +@patch("scripts.leapp_upgrade.get_rhel_version") +@patch("scripts.leapp_upgrade.is_non_eligible_releases") +@patch("scripts.leapp_upgrade.OutputCollector") +def test_main_non_eligible_release( + mock_output_collector, mock_is_non_eligible_releases, mock_get_rhel_version +): + mock_get_rhel_version.return_value = ("rhel", "6.9") + mock_is_non_eligible_releases.return_value = True + + with pytest.raises(ProcessError): + main() + + mock_get_rhel_version.assert_called_once() + mock_is_non_eligible_releases.assert_called_once() + mock_output_collector.assert_not_called() + + +@patch("scripts.leapp_upgrade.parse_results") +@patch("scripts.leapp_upgrade.reboot_system") +@patch("scripts.leapp_upgrade.get_rhel_version") +@patch("scripts.leapp_upgrade.is_non_eligible_releases") +@patch("scripts.leapp_upgrade.setup_leapp") +@patch("scripts.leapp_upgrade.should_use_no_rhsm_check") +@patch("scripts.leapp_upgrade.install_leapp_pkg_corresponding_to_installed_rhui") +@patch("scripts.leapp_upgrade.remove_previous_reports") +@patch("scripts.leapp_upgrade.execute_upgrade") +@patch("scripts.leapp_upgrade.call_insights_client") +@patch("scripts.leapp_upgrade.OutputCollector") +def test_main_eligible_release( + mock_output_collector, + mock_call_insights_client, + mock_execute_upgrade, + mock_remove_previous_reports, + mock_should_use_no_rhsm_check, + mock_install_rhui, + mock_setup_leapp, + mock_is_non_eligible_releases, + mock_get_rhel_version, + mock_reboot_system, + mock_parse_results, +): + mock_get_rhel_version.return_value = ("rhel", "7.9") + mock_is_non_eligible_releases.return_value = False + mock_setup_leapp.return_value = [{"leapp_pkg": "to_install"}] + mock_should_use_no_rhsm_check.return_value = True + mock_output_collector.return_value = OutputCollector(entries=["non-empty"]) + mock_execute_upgrade.return_value = ( + "LOREM IPSUM\nTEST" + REBOOT_GUIDANCE_MESSAGE + "TEST\nDOLOR SIT AMET" + ) + + main() + + mock_setup_leapp.assert_called_once() + mock_should_use_no_rhsm_check.assert_called_once() + mock_install_rhui.assert_called_once() + mock_remove_previous_reports.assert_called_once() + mock_execute_upgrade.assert_called_once() + mock_parse_results.assert_called_once() + mock_call_insights_client.assert_called_once() + mock_reboot_system.assert_called_once() + + +@patch("scripts.leapp_upgrade.parse_results") +@patch("scripts.leapp_upgrade.reboot_system") +@patch("scripts.leapp_upgrade.get_rhel_version") +@patch("scripts.leapp_upgrade.is_non_eligible_releases") +@patch("scripts.leapp_upgrade.setup_leapp") +@patch("scripts.leapp_upgrade.should_use_no_rhsm_check") +@patch("scripts.leapp_upgrade.install_leapp_pkg_corresponding_to_installed_rhui") +@patch("scripts.leapp_upgrade.remove_previous_reports") +@patch("scripts.leapp_upgrade.execute_upgrade") +@patch("scripts.leapp_upgrade.call_insights_client") +@patch("scripts.leapp_upgrade.OutputCollector") +def test_main_upgrade_not_sucessfull( + mock_output_collector, + mock_call_insights_client, + mock_execute_upgrade, + mock_remove_previous_reports, + mock_should_use_no_rhsm_check, + mock_install_rhui, + mock_setup_leapp, + mock_is_non_eligible_releases, + mock_get_rhel_version, + mock_reboot_system, + mock_parse_results, +): + mock_get_rhel_version.return_value = ("rhel", "7.9") + mock_is_non_eligible_releases.return_value = False + mock_setup_leapp.return_value = [{"leapp_pkg": "to_install"}] + mock_should_use_no_rhsm_check.return_value = True + mock_output_collector.return_value = OutputCollector(entries=["non-empty"]) + mock_execute_upgrade.return_value = "LOREM IPSUM\n" + "\nDOLOR SIT AMET" + + main() + + mock_setup_leapp.assert_called_once() + mock_should_use_no_rhsm_check.assert_called_once() + mock_install_rhui.assert_called_once() + mock_remove_previous_reports.assert_called_once() + mock_execute_upgrade.assert_called_once() + mock_parse_results.assert_called_once() + mock_call_insights_client.assert_called_once() + mock_reboot_system.assert_not_called() diff --git a/tests/upgrade/test_non_eligible_release.py b/tests/upgrade/test_non_eligible_release.py new file mode 100644 index 0000000..078caa4 --- /dev/null +++ b/tests/upgrade/test_non_eligible_release.py @@ -0,0 +1,12 @@ +from scripts.leapp_upgrade import is_non_eligible_releases + + +def test_is_non_eligible_releases(): + eligible_releases = ["7.9", "8.4"] + non_eligible_releases = ["6.10", "9.0", "10.0"] + + for release in eligible_releases: + assert not is_non_eligible_releases(release) + + for release in non_eligible_releases: + assert is_non_eligible_releases(release) diff --git a/tests/upgrade/test_parse_results.py b/tests/upgrade/test_parse_results.py new file mode 100644 index 0000000..8a4f143 --- /dev/null +++ b/tests/upgrade/test_parse_results.py @@ -0,0 +1,41 @@ +from mock import mock_open, patch + +from scripts.leapp_upgrade import ( + parse_results, + OutputCollector, +) + + +@patch("os.path.exists", return_value=True) +@patch("scripts.leapp_upgrade.find_highest_report_level", return_value="ERROR") +def test_gather_report_files_exist(mock_find_level, mock_exists): + test_txt_content = "Test data" + test_json_content = '{"test": "hi"}' + output = OutputCollector() + with patch("__builtin__.open") as mock_open_reports: + return_values = [test_json_content, test_txt_content] + mock_open_reports.side_effect = lambda file, mode: mock_open( + read_data=return_values.pop(0) + )(file, mode) + parse_results(output) + + assert mock_find_level.call_count == 0 # entries do not exists -> [] + assert output.status == "SUCCESS" + assert mock_exists.call_count == 2 + assert output.report == test_txt_content + assert output.report_json.get("test") == "hi" + # NOTE: is this right? + assert output.message == "Your system has 0 inhibitors out of 0 potential problems." + + +@patch("os.path.exists", return_value=False) +def test_gather_report_files_not_exist(mock_exists): + output = OutputCollector() + with patch("__builtin__.open") as mock_open_reports: + parse_results(output) + mock_open_reports.assert_not_called() + + assert mock_exists.call_count == 2 + assert output.report != "" + assert output.report_json != "" + assert output.message != "" diff --git a/tests/upgrade/test_reboot.py b/tests/upgrade/test_reboot.py new file mode 100644 index 0000000..7db56d1 --- /dev/null +++ b/tests/upgrade/test_reboot.py @@ -0,0 +1,9 @@ +from mock import patch + +from scripts.leapp_upgrade import reboot_system + + +@patch("scripts.leapp_upgrade.run_subprocess", return_value=(b"", 0)) +def test_call_insights_client(mock_popen): + reboot_system() + mock_popen.assert_called_once_with(["shutdown", "-r", "1"], wait=False) diff --git a/tests/upgrade/test_remove_reports.py b/tests/upgrade/test_remove_reports.py new file mode 100644 index 0000000..908fae2 --- /dev/null +++ b/tests/upgrade/test_remove_reports.py @@ -0,0 +1,23 @@ +from mock import patch +from scripts.leapp_upgrade import ( + remove_previous_reports, +) + + +@patch("os.path.exists", side_effect=[True, True]) +@patch("os.remove") +def test_remove_previous_reports_with_files(mock_remove, _): + json_report_path = "/var/log/leapp/leapp-report.json" + text_report_path = "/var/log/leapp/leapp-report.txt" + + remove_previous_reports() + + mock_remove.assert_any_call(json_report_path) + mock_remove.assert_any_call(text_report_path) + + +@patch("os.path.exists", side_effect=[False, False]) +@patch("os.remove") +def test_remove_previous_reports_without_files(mock_remove, _): + remove_previous_reports() + mock_remove.assert_not_called() diff --git a/tests/upgrade/test_run.py b/tests/upgrade/test_run.py new file mode 100644 index 0000000..2fa1835 --- /dev/null +++ b/tests/upgrade/test_run.py @@ -0,0 +1,10 @@ +from mock import patch + +from scripts.leapp_upgrade import execute_upgrade + + +@patch("scripts.leapp_upgrade.run_subprocess", return_value=(b"", 0)) +def test_run_leapp_preupgrade(mock_popen): + execute_upgrade(["fake command"]) + + mock_popen.assert_called_once_with(["fake command"]) diff --git a/tests/upgrade/test_run_subprocess.py b/tests/upgrade/test_run_subprocess.py new file mode 100644 index 0000000..e284bdd --- /dev/null +++ b/tests/upgrade/test_run_subprocess.py @@ -0,0 +1,97 @@ +# Taken from https://github.com/oamg/convert2rhel-worker-scripts/blob/main/tests/preconversion_assessment/test_misc.py +import subprocess +import pytest +from mock import patch + +from scripts.leapp_upgrade import run_subprocess + + +class SubprocessMock(object): + def __init__(self, output, returncode): + self.call_count = 0 + self.output = output + self.returncode = returncode + + def __call__(self, args, stdout, stderr, bufsize): + return self + + @property + def stdout(self): + return self + + def readline(self): + try: + next_line = self.output[self.call_count] + except IndexError: + return b"" + + self.call_count += 1 + return next_line + + def wait(self): + pass + + +def test_run_subprocess_cmd_as_list(): + cmd = "echo 'test'" + + with pytest.raises(TypeError, match="cmd should be a list, not a str"): + run_subprocess(cmd) + + +@pytest.mark.parametrize( + ("subprocess_out", "expected"), + ( + (("output".encode("utf-8"), 0), ("output", 0)), + (("output".encode("utf-8"), 1), ("output", 1)), + ), +) +def test_run_subprocess(subprocess_out, expected): + with patch( + "subprocess.Popen", + return_value=SubprocessMock(subprocess_out[0], subprocess_out[1]), + ) as mocked_subprocess: + output, returncode = run_subprocess(["test", "hi"], print_cmd=True) + + assert (output, returncode) == expected + mocked_subprocess.assert_called_once_with( + ["test", "hi"], + bufsize=1, + env=None, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + ) + + +def test_run_subprocess_custom_env(): + with patch( + "subprocess.Popen", return_value=SubprocessMock("output".encode("utf-8"), 0) + ) as mocked_subprocess: + output, returncode = run_subprocess( + ["test", "hi"], print_cmd=False, env={"PATH": "/fake_path"} + ) + + assert (output, returncode) == ("output", 0) + mocked_subprocess.assert_called_once_with( + ["test", "hi"], + env={"PATH": "/fake_path"}, + bufsize=1, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + ) + + +def test_run_subprocess_custom_not_wait(): + with patch( + "subprocess.Popen", return_value=SubprocessMock("output".encode("utf-8"), 0) + ) as mocked_subprocess: + output, returncode = run_subprocess(["test", "hi"], wait=False) + + assert (output, returncode) == ("", None) + mocked_subprocess.assert_called_once_with( + ["test", "hi"], + env=None, + bufsize=1, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + ) diff --git a/tests/upgrade/test_schema.py b/tests/upgrade/test_schema.py new file mode 100644 index 0000000..b5c36c9 --- /dev/null +++ b/tests/upgrade/test_schema.py @@ -0,0 +1,32 @@ +import json +import jsonschema +from scripts.leapp_upgrade import OutputCollector, STATUS_CODE_NAME_MAP + + +def test_output_schema(): + """Test that pre-upgrade report is wrapped in expected json""" + for status in STATUS_CODE_NAME_MAP.values(): + output_collector = OutputCollector(status=status) + empty_output = output_collector.to_dict() + + with open("schemas/preupgrade_schema_1.0.json", "r") as schema: + schema_json = json.load(schema) + # If some difference between generated json and its schema invoke exception + jsonschema.validate(instance=empty_output, schema=schema_json) + + +def test_output_schema_entries_report(): + """ + Test that pre-upgrade report is wrapped in expected json with entries + key. + """ + output_collector = OutputCollector(status="WARNING") + # Not close to a real json returned by the assessment report, but we don't + # have to check that here. + output_collector.entries = {"hi": "world"} + full_output = output_collector.to_dict() + + with open("schemas/preupgrade_schema_1.0.json", "r") as schema: + schema_json = json.load(schema) + # If some difference between generated json and its schema invoke exception + jsonschema.validate(instance=full_output, schema=schema_json) diff --git a/tests/upgrade/test_setup_leapp.py b/tests/upgrade/test_setup_leapp.py new file mode 100644 index 0000000..9149c95 --- /dev/null +++ b/tests/upgrade/test_setup_leapp.py @@ -0,0 +1,36 @@ +import pytest +from mock import patch +from scripts.leapp_upgrade import setup_leapp, ProcessError + + +@patch("scripts.leapp_upgrade.run_subprocess") +@patch("scripts.leapp_upgrade.check_if_package_installed") +def test_setup_leapp_success(mock_check_if_package_installed, mock_run_subprocess): + mock_run_subprocess.return_value = ("Installation successful", 0) + mock_check_if_package_installed.return_value = True + + command = ["dnf", "install", "leapp-upgrade", "-y"] + rhui_pkgs = [{"src_pkg": "rh-amazon-rhui-client"}, {"src_pkg": "rhui-azure-rhel8"}] + + result = setup_leapp(command, rhui_pkgs) + + assert mock_check_if_package_installed.call_count == 2 + mock_run_subprocess.assert_called_once_with(command) + assert all(pkg.get("installed", False) for pkg in result) + + +@patch("scripts.leapp_upgrade.run_subprocess") +@patch("scripts.leapp_upgrade.check_if_package_installed") +def test_setup_leapp_failure(mock_check_if_package_installed, mock_run_subprocess): + mock_run_subprocess.return_value = ("Installation failed", 1) + mock_check_if_package_installed.return_value = True + + command = ["dnf", "install", "leapp-upgrade", "-y"] + rhui_pkgs = [{"src_pkg": "rh-amazon-rhui-client"}, {"src_pkg": "rhui-azure-rhel8"}] + + with pytest.raises(ProcessError) as e_info: + setup_leapp(command, rhui_pkgs) + + mock_run_subprocess.assert_called_once_with(command) + assert mock_check_if_package_installed.call_count == 0 + assert str(e_info.value) == "Installation of leapp failed with code '1'." diff --git a/tests/upgrade/test_should_use_rhsm.py b/tests/upgrade/test_should_use_rhsm.py new file mode 100644 index 0000000..69f9130 --- /dev/null +++ b/tests/upgrade/test_should_use_rhsm.py @@ -0,0 +1,50 @@ +from mock import patch +from scripts.leapp_upgrade import should_use_no_rhsm_check + + +@patch("scripts.leapp_upgrade.run_subprocess") +def test_should_use_no_rhsm_rhsm_and_rhui_installed( + mock_run_subprocess, +): + mock_run_subprocess.side_effect = [ + ("/path/to/subscription-manager\n", 0), + ("output_of_subscription_manager_repos_command", 0), + ] + + rhui_installed = True + command = ["preupgrade"] + result = should_use_no_rhsm_check(rhui_installed, command) + + mock_run_subprocess.call_count = 2 + assert result + + +@patch("scripts.leapp_upgrade.run_subprocess") +def test_should_use_no_rhsm_rhsm_installed_rhui_not( + mock_run_subprocess, +): + mock_run_subprocess.side_effect = [ + ("/path/to/subscription-manager\n", 0), + ("output_of_subscription_manager_repos_command", 0), + ] + + rhui_installed = False + command = ["preupgrade"] + result = should_use_no_rhsm_check(rhui_installed, command) + + mock_run_subprocess.call_count = 2 + assert not result + + +@patch("scripts.leapp_upgrade.run_subprocess") +def test_should_use_no_rhsm_rhsm_not_installed(mock_run_subprocess): + mock_run_subprocess.side_effect = [ + ("error_message", 1), + ] + + rhui_installed = True + command = ["preupgrade"] + result = should_use_no_rhsm_check(rhui_installed, command) + + mock_run_subprocess.assert_called_once() + assert not result From f358a66c6dc45f980037edb448fa99a3f2e2fb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Zdraveck=C3=BD?= Date: Thu, 16 Nov 2023 14:29:42 +0100 Subject: [PATCH 3/5] Modify upgrade script and tests based on feedback --- schemas/preupgrade_schema_1.0.json | 1 + scripts/leapp_upgrade.py | 275 +++++++++++------- tests/upgrade/test_check_pkg_installed.py | 6 +- tests/upgrade/test_collect_report_level.py | 6 +- tests/upgrade/test_insights_client_call.py | 9 - tests/upgrade/test_install_rhui_pkgs.py | 8 +- tests/upgrade/test_main.py | 30 +- tests/upgrade/test_parse_results.py | 2 +- tests/upgrade/test_reboot.py | 4 +- tests/upgrade/test_schema.py | 2 + tests/upgrade/test_setup_leapp.py | 51 ++-- .../upgrade/test_update_insights_inventory.py | 18 ++ 12 files changed, 249 insertions(+), 163 deletions(-) delete mode 100644 tests/upgrade/test_insights_client_call.py create mode 100644 tests/upgrade/test_update_insights_inventory.py diff --git a/schemas/preupgrade_schema_1.0.json b/schemas/preupgrade_schema_1.0.json index 8096710..86bf6a9 100644 --- a/schemas/preupgrade_schema_1.0.json +++ b/schemas/preupgrade_schema_1.0.json @@ -67,3 +67,4 @@ } } } +} diff --git a/scripts/leapp_upgrade.py b/scripts/leapp_upgrade.py index b2c47f2..15412da 100644 --- a/scripts/leapp_upgrade.py +++ b/scripts/leapp_upgrade.py @@ -27,16 +27,30 @@ class ProcessError(Exception): """Custom exception to report errors during setup and run of leapp""" - def __init__(self, message): - super(ProcessError, self).__init__(message) + def __init__(self, message, report): + super(ProcessError, self).__init__(report) self.message = message + self.report = report class OutputCollector(object): """Wrapper class for script expected stdout""" - def __init__(self, status="", message="", report="", entries=None): + # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-arguments + # Nine and six is reasonable in this case. + + def __init__( + self, status="", message="", report="", entries=None, alert=False, error=False + ): self.status = status + self.alert = alert # true if error true or if pre-upgrade inhibited + + # NOTE: currently false everywhere + # here for consistency with conversions scripts + # expected to change after tasks implement new statuses + self.error = error + self.message = message self.report = report self.tasks_format_version = "1.0" @@ -57,6 +71,8 @@ def to_dict(self): return { "status": self.status, + "alert": self.alert, + "error": self.error, "message": self.message, "report": self.report, "report_json": self.report_json, @@ -82,9 +98,9 @@ def get_rhel_version(): def is_non_eligible_releases(release): print("Exit if not RHEL 7.9 or 8.4") + eligible_releases = ["7.9", "8.4"] major_version, minor = release.split(".") if release is not None else (None, None) version_str = major_version + "." + minor - eligible_releases = ["7.9", "8.4"] return release is None or version_str not in eligible_releases @@ -128,37 +144,102 @@ def run_subprocess(cmd, print_cmd=True, env=None, wait=True): return output, process.returncode -def check_if_package_installed(pkg_name): - _, return_code = run_subprocess(["rpm", "-q", pkg_name]) +def _check_if_package_installed(pkg_name): + _, return_code = run_subprocess(["/usr/bin/rpm", "-q", pkg_name]) return return_code == 0 -def setup_leapp(command, rhui_pkgs): +def _get_leapp_command_and_packages(version): + if version.startswith("7"): + leapp_install_command = [ + "/usr/bin/yum", + "install", + "leapp-upgrade", + "-y", + "--enablerepo=rhel-7-server-extras-rpms", + ] + rhui_packages = [ + {"src_pkg": "rh-amazon-rhui-client", "leapp_pkg": "leapp-rhui-aws"}, + { + "src_pkg": "rh-amazon-rhui-client-sap-bundle", + "leapp_pkg": "leapp-rhui-aws-sap-e4s", + }, + {"src_pkg": "rhui-azure-rhel7", "leapp_pkg": "leapp-rhui-azure"}, + { + "src_pkg": "rhui-azure-rhel7-base-sap-apps", + "leapp_pkg": "leapp-rhui-azure-sap", + }, + { + "src_pkg": "rhui-azure-rhel7-base-sap-ha", + "leapp_pkg": "leapp-rhui-azure-sap", + }, + { + "src_pkg": "google-rhui-client-rhel7", + "leapp_pkg": "leapp-rhui-google", + }, + { + "src_pkg": "google-rhui-client-rhel79-sap", + "leapp_pkg": "leapp-rhui-google-sap", + }, + ] + if version.startswith("8"): + leapp_install_command = ["/usr/bin/dnf", "install", "leapp-upgrade", "-y"] + rhui_packages = [ + {"src_pkg": "rh-amazon-rhui-client", "leapp_pkg": "leapp-rhui-aws"}, + { + "src_pkg": "rh-amazon-rhui-client-sap-bundle-e4s", + "leapp_pkg": "leapp-rhui-aws-sap-e4s", + }, + {"src_pkg": "rhui-azure-rhel8", "leapp_pkg": "leapp-rhui-azure"}, + { + "src_pkg": "rhui-azure-rhel8-eus", + "leapp_pkg": "leapp-rhui-azure-eus", + }, + { + "src_pkg": "rhui-azure-rhel8-sap-ha", + "leapp_pkg": "leapp-rhui-azure-sap", + }, + { + "src_pkg": "rhui-azure-rhel8-sapapps", + "leapp_pkg": "leapp-rhui-azure-sap", + }, + { + "src_pkg": "google-rhui-client-rhel8", + "leapp_pkg": "leapp-rhui-google", + }, + { + "src_pkg": "google-rhui-client-rhel8-sap", + "leapp_pkg": "leapp-rhui-google-sap", + }, + ] + return leapp_install_command, rhui_packages + + +def setup_leapp(version): print("Installing leapp ...") - output, returncode = run_subprocess(command) + leapp_install_command, rhel_rhui_packages = _get_leapp_command_and_packages(version) + output, returncode = run_subprocess(leapp_install_command) if returncode: - print( - "Installation of leapp failed with code '%s' and output: %s\n" - % (returncode, output) - ) raise ProcessError( - message="Installation of leapp failed with code '%s'." % returncode + message="Installation of leapp failed", + report="Installation of leapp failed with code '%s' and output: %s." + % (returncode, output.rstrip("\n")), ) print("Check installed rhui packages ...") - for pkg in rhui_pkgs: - if check_if_package_installed(pkg["src_pkg"]): + for pkg in rhel_rhui_packages: + if _check_if_package_installed(pkg["src_pkg"]): pkg["installed"] = True - return [pkg for pkg in rhui_pkgs if pkg.get("installed", False)] + return [pkg for pkg in rhel_rhui_packages if pkg.get("installed", False)] def should_use_no_rhsm_check(rhui_installed, command): print("Checking if subscription manager and repositories are available ...") rhsm_repo_check_fail = True - _, rhsm_installed_check = run_subprocess(["which", "subscription-manager"]) - if rhsm_installed_check == 0: + rhsm_installed_check = _check_if_package_installed("subscription-manager") + if rhsm_installed_check: rhsm_repo_check, _ = run_subprocess( - ["subscription-manager", "repos", "--list-enabled"] + ["/usr/sbin/subscription-manager", "repos", "--list-enabled"] ) rhsm_repo_check_fail = ( "This system has no repositories available through subscriptions." @@ -178,13 +259,13 @@ def install_leapp_pkg_corresponding_to_installed_rhui(rhui_pkgs): for pkg in rhui_pkgs: install_pkg = pkg["leapp_pkg"] install_output, returncode = run_subprocess( - ["yum", "install", "-y", install_pkg] + ["/usr/bin/yum", "install", "-y", install_pkg] ) if returncode: - print("Installation of %s failed. \n%s" % (install_pkg, install_output)) raise ProcessError( - message="Installation of %s (coresponding pkg to '%s') failed with exit code %s." - % (install_pkg, pkg, returncode) + message="Installation of %s (coresponding pkg to '%s') failed", + report="Installation of %s (coresponding pkg to '%s') failed with exit code %s and output: %s." + % (install_pkg, pkg, returncode, install_output.rstrip("\n")), ) @@ -204,8 +285,16 @@ def execute_upgrade(command): return output + # NOTE: we do not care about returncode because non-null always means actor error (or leapp error) + # if returncode: + # print( + # "The process leapp exited with code '%s' and output: %s\n" + # % (returncode, output) + # ) + # raise ProcessError(message="Leapp exited with code '%s'." % returncode) + -def find_highest_report_level(entries): +def _find_highest_report_level(entries): """ Gather status codes from entries. """ @@ -245,7 +334,7 @@ def parse_results(output): ) alert = inhibitor_count > 0 status = ( - find_highest_report_level(report_entries) + _find_highest_report_level(report_entries) if len(report_entries) > 0 else "SUCCESS" ) @@ -264,118 +353,82 @@ def parse_results(output): output.report = report_txt -def call_insights_client(): - print("Calling insight-client in background for immediate data collection.") - run_subprocess(["insights-client"], wait=False) - # NOTE: we do not care about returncode or output because we are not waiting for process to finish +def update_insights_inventory(): + """Call insights-client to update insights inventory.""" + print("Updating system status in Red Hat Insights.") + output, returncode = run_subprocess(["/usr/bin/insights-client"]) + + if returncode: + raise ProcessError( + message="Failed to update Insights Inventory by registering the system again. See output the following output: %s" + % output, + report="insights-client execution exited with code '%s'." % returncode, + ) + + print("System registered with insights-client successfully.") def reboot_system(): print("Rebooting system in 1 minute.") - run_subprocess(["shutdown", "-r", "1"], wait=False) + run_subprocess(["/usr/sbin/shutdown", "-r", "1"], wait=False) def main(): - dist, version = get_rhel_version() - if dist != "rhel" or is_non_eligible_releases(version): - raise ProcessError( - message='Exiting because distribution="%s" and version="%s"' - % (dist, version) - ) - - output = OutputCollector() leapp_upgrade_output = None try: - # Init variables - upgrade_command = ["/usr/bin/leapp", "upgrade"] - use_no_rhsm = False - rhui_pkgs = [] - if version.startswith("7"): - leapp_install_command = [ - "yum", - "install", - "leapp-upgrade", - "-y", - "--enablerepo=rhel-7-server-extras-rpms", - ] - rhel_7_rhui_packages = [ - {"src_pkg": "rh-amazon-rhui-client", "leapp_pkg": "leapp-rhui-aws"}, - { - "src_pkg": "rh-amazon-rhui-client-sap-bundle", - "leapp_pkg": "leapp-rhui-aws-sap-e4s", - }, - {"src_pkg": "rhui-azure-rhel7", "leapp_pkg": "leapp-rhui-azure"}, - { - "src_pkg": "rhui-azure-rhel7-base-sap-apps", - "leapp_pkg": "leapp-rhui-azure-sap", - }, - { - "src_pkg": "rhui-azure-rhel7-base-sap-ha", - "leapp_pkg": "leapp-rhui-azure-sap", - }, - { - "src_pkg": "google-rhui-client-rhel7", - "leapp_pkg": "leapp-rhui-google", - }, - { - "src_pkg": "google-rhui-client-rhel79-sap", - "leapp_pkg": "leapp-rhui-google-sap", - }, - ] - rhui_pkgs = setup_leapp(leapp_install_command, rhel_7_rhui_packages) - if version.startswith("8"): - leapp_install_command = ["dnf", "install", "leapp-upgrade", "-y"] - rhel_8_rhui_packages = [ - {"src_pkg": "rh-amazon-rhui-client", "leapp_pkg": "leapp-rhui-aws"}, - { - "src_pkg": "rh-amazon-rhui-client-sap-bundle-e4s", - "leapp_pkg": "leapp-rhui-aws-sap-e4s", - }, - {"src_pkg": "rhui-azure-rhel8", "leapp_pkg": "leapp-rhui-azure"}, - { - "src_pkg": "rhui-azure-rhel8-eus", - "leapp_pkg": "leapp-rhui-azure-eus", - }, - { - "src_pkg": "rhui-azure-rhel8-sap-ha", - "leapp_pkg": "leapp-rhui-azure-sap", - }, - { - "src_pkg": "rhui-azure-rhel8-sapapps", - "leapp_pkg": "leapp-rhui-azure-sap", - }, - { - "src_pkg": "google-rhui-client-rhel8", - "leapp_pkg": "leapp-rhui-google", - }, - { - "src_pkg": "google-rhui-client-rhel8-sap", - "leapp_pkg": "leapp-rhui-google-sap", - }, - ] - rhui_pkgs = setup_leapp(leapp_install_command, rhel_8_rhui_packages) + # Exit if not RHEL 7.9 or 8.4 + dist, version = get_rhel_version() + if dist != "rhel" or is_non_eligible_releases(version): + raise ProcessError( + message="In-place upgrades are supported only on RHEL distributions.", + report='Exiting because distribution="%s" and version="%s"' + % (dist, version), + ) - use_no_rhsm = should_use_no_rhsm_check(len(rhui_pkgs) > 1, upgrade_command) + output = OutputCollector() + upgrade_command = ["/usr/bin/leapp", "upgrade", "--report-schema=1.1.0"] + rhui_pkgs = setup_leapp(version) + # Check for RHUI PKGs + use_no_rhsm = should_use_no_rhsm_check(len(rhui_pkgs) > 1, upgrade_command) if use_no_rhsm: install_leapp_pkg_corresponding_to_installed_rhui(rhui_pkgs) remove_previous_reports() leapp_upgrade_output = execute_upgrade(upgrade_command) - print("Upgrade successfully executed.") + print("Leapp upgrade command successfully executed.") parse_results(output) except ProcessError as exception: - output = OutputCollector(status="ERROR", report=exception.message) + print(exception.report) + output = OutputCollector( + status="ERROR", + alert=True, + error=False, + message=exception.message, + report=exception.report, + ) + leapp_upgrade_output = None except Exception as exception: - output = OutputCollector(status="ERROR", report=str(exception)) + print(str(exception)) + output = OutputCollector( + status="ERROR", + alert=True, + error=False, + message="An unexpected error occurred. Expand the row for more details.", + report=str(exception), + ) + leapp_upgrade_output = None finally: print("### JSON START ###") print(json.dumps(output.to_dict(), indent=4)) print("### JSON END ###") - call_insights_client() + update_insights_inventory() if leapp_upgrade_output and REBOOT_GUIDANCE_MESSAGE in leapp_upgrade_output: + print("System is ready to be upgraded. Reboot is required.") reboot_system() + else: + print("System is NOT ready to be upgraded.") if __name__ == "__main__": diff --git a/tests/upgrade/test_check_pkg_installed.py b/tests/upgrade/test_check_pkg_installed.py index 4dc162b..1f5580c 100644 --- a/tests/upgrade/test_check_pkg_installed.py +++ b/tests/upgrade/test_check_pkg_installed.py @@ -1,13 +1,13 @@ from mock import patch -from scripts.leapp_upgrade import check_if_package_installed +from scripts.leapp_upgrade import _check_if_package_installed @patch("scripts.leapp_upgrade.run_subprocess") def test_check_if_package_installed(mock_run_subprocess): pkg_name = "example-package" mock_run_subprocess.return_value = ("", 0) - result = check_if_package_installed(pkg_name) - expected_command = ["rpm", "-q", pkg_name] + result = _check_if_package_installed(pkg_name) + expected_command = ["/usr/bin/rpm", "-q", pkg_name] mock_run_subprocess.assert_called_once_with(expected_command) assert result diff --git a/tests/upgrade/test_collect_report_level.py b/tests/upgrade/test_collect_report_level.py index 54d64fa..a0d6ac7 100644 --- a/tests/upgrade/test_collect_report_level.py +++ b/tests/upgrade/test_collect_report_level.py @@ -1,6 +1,6 @@ import pytest from scripts.leapp_upgrade import ( - find_highest_report_level, + _find_highest_report_level, ) @@ -41,7 +41,7 @@ ) def test_find_highest_report_level_expected(entries, expected): """Should be sorted descending from the highest status to the lower one.""" - result = find_highest_report_level(entries) + result = _find_highest_report_level(entries) assert result == expected @@ -53,5 +53,5 @@ def test_find_highest_report_level_unknown_status(): {"severity": "medium"}, {"severity": "foo"}, ] - result = find_highest_report_level(action_results_test) + result = _find_highest_report_level(action_results_test) assert result == expected_output diff --git a/tests/upgrade/test_insights_client_call.py b/tests/upgrade/test_insights_client_call.py deleted file mode 100644 index 9ca4909..0000000 --- a/tests/upgrade/test_insights_client_call.py +++ /dev/null @@ -1,9 +0,0 @@ -from mock import patch - -from scripts.leapp_upgrade import call_insights_client - - -@patch("scripts.leapp_upgrade.run_subprocess", return_value=(b"", 0)) -def test_call_insights_client(mock_popen): - call_insights_client() - mock_popen.assert_called_once_with(["insights-client"], wait=False) diff --git a/tests/upgrade/test_install_rhui_pkgs.py b/tests/upgrade/test_install_rhui_pkgs.py index 5a101d9..b83a7a3 100644 --- a/tests/upgrade/test_install_rhui_pkgs.py +++ b/tests/upgrade/test_install_rhui_pkgs.py @@ -15,12 +15,12 @@ def test_install_leapp_pkg_to_installed_rhui(mock_run_subprocess): install_leapp_pkg_corresponding_to_installed_rhui(rhui_pkgs) for pkg in rhui_pkgs: - expected_command = ["yum", "install", "-y", pkg["leapp_pkg"]] + expected_command = ["/usr/bin/yum", "install", "-y", pkg["leapp_pkg"]] assert call(expected_command) in mock_run_subprocess.call_args_list @patch("scripts.leapp_upgrade.run_subprocess") -def test_install_leapp_pkge_to_installed_rhui_error( +def test_install_leapp_pkg_to_installed_rhui_error( mock_run_subprocess, ): rhui_pkgs = [{"leapp_pkg": "pkg1"}, {"leapp_pkg": "pkg2"}] @@ -29,10 +29,10 @@ def test_install_leapp_pkge_to_installed_rhui_error( with pytest.raises(ProcessError) as exception: install_leapp_pkg_corresponding_to_installed_rhui(rhui_pkgs) - expected_command = ["yum", "install", "-y", "pkg1"] + expected_command = ["/usr/bin/yum", "install", "-y", "pkg1"] mock_run_subprocess.assert_called_once_with(expected_command) assert ( str(exception.value) - == "Installation of pkg1 (coresponding pkg to '{'leapp_pkg': 'pkg1'}') failed with exit code 1." + == "Installation of pkg1 (coresponding pkg to '{'leapp_pkg': 'pkg1'}') failed with exit code 1 and output: Installation failed." ) diff --git a/tests/upgrade/test_main.py b/tests/upgrade/test_main.py index 9fbbcd4..ff4c406 100644 --- a/tests/upgrade/test_main.py +++ b/tests/upgrade/test_main.py @@ -1,8 +1,6 @@ -import pytest from mock import patch from scripts.leapp_upgrade import ( main, - ProcessError, OutputCollector, REBOOT_GUIDANCE_MESSAGE, ) @@ -10,19 +8,27 @@ @patch("scripts.leapp_upgrade.get_rhel_version") @patch("scripts.leapp_upgrade.is_non_eligible_releases") +@patch("scripts.leapp_upgrade.setup_leapp") +@patch("scripts.leapp_upgrade.update_insights_inventory") @patch("scripts.leapp_upgrade.OutputCollector") def test_main_non_eligible_release( - mock_output_collector, mock_is_non_eligible_releases, mock_get_rhel_version + mock_output_collector, + mock_update_insights_inventory, + mock_setup_leapp, + mock_is_non_eligible_releases, + mock_get_rhel_version, ): mock_get_rhel_version.return_value = ("rhel", "6.9") mock_is_non_eligible_releases.return_value = True + mock_output_collector.return_value = OutputCollector(entries=["non-empty"]) - with pytest.raises(ProcessError): - main() + main() mock_get_rhel_version.assert_called_once() mock_is_non_eligible_releases.assert_called_once() - mock_output_collector.assert_not_called() + mock_output_collector.assert_called_once() + mock_setup_leapp.assert_not_called() + mock_update_insights_inventory.assert_called_once() @patch("scripts.leapp_upgrade.parse_results") @@ -34,11 +40,11 @@ def test_main_non_eligible_release( @patch("scripts.leapp_upgrade.install_leapp_pkg_corresponding_to_installed_rhui") @patch("scripts.leapp_upgrade.remove_previous_reports") @patch("scripts.leapp_upgrade.execute_upgrade") -@patch("scripts.leapp_upgrade.call_insights_client") +@patch("scripts.leapp_upgrade.update_insights_inventory") @patch("scripts.leapp_upgrade.OutputCollector") def test_main_eligible_release( mock_output_collector, - mock_call_insights_client, + mock_update_insights_inventory, mock_execute_upgrade, mock_remove_previous_reports, mock_should_use_no_rhsm_check, @@ -66,7 +72,7 @@ def test_main_eligible_release( mock_remove_previous_reports.assert_called_once() mock_execute_upgrade.assert_called_once() mock_parse_results.assert_called_once() - mock_call_insights_client.assert_called_once() + mock_update_insights_inventory.assert_called_once() mock_reboot_system.assert_called_once() @@ -79,11 +85,11 @@ def test_main_eligible_release( @patch("scripts.leapp_upgrade.install_leapp_pkg_corresponding_to_installed_rhui") @patch("scripts.leapp_upgrade.remove_previous_reports") @patch("scripts.leapp_upgrade.execute_upgrade") -@patch("scripts.leapp_upgrade.call_insights_client") +@patch("scripts.leapp_upgrade.update_insights_inventory") @patch("scripts.leapp_upgrade.OutputCollector") def test_main_upgrade_not_sucessfull( mock_output_collector, - mock_call_insights_client, + mock_update_insights_inventory, mock_execute_upgrade, mock_remove_previous_reports, mock_should_use_no_rhsm_check, @@ -109,5 +115,5 @@ def test_main_upgrade_not_sucessfull( mock_remove_previous_reports.assert_called_once() mock_execute_upgrade.assert_called_once() mock_parse_results.assert_called_once() - mock_call_insights_client.assert_called_once() + mock_update_insights_inventory.assert_called_once() mock_reboot_system.assert_not_called() diff --git a/tests/upgrade/test_parse_results.py b/tests/upgrade/test_parse_results.py index 8a4f143..ca379fc 100644 --- a/tests/upgrade/test_parse_results.py +++ b/tests/upgrade/test_parse_results.py @@ -7,7 +7,7 @@ @patch("os.path.exists", return_value=True) -@patch("scripts.leapp_upgrade.find_highest_report_level", return_value="ERROR") +@patch("scripts.leapp_upgrade._find_highest_report_level", return_value="ERROR") def test_gather_report_files_exist(mock_find_level, mock_exists): test_txt_content = "Test data" test_json_content = '{"test": "hi"}' diff --git a/tests/upgrade/test_reboot.py b/tests/upgrade/test_reboot.py index 7db56d1..0c3dc07 100644 --- a/tests/upgrade/test_reboot.py +++ b/tests/upgrade/test_reboot.py @@ -4,6 +4,6 @@ @patch("scripts.leapp_upgrade.run_subprocess", return_value=(b"", 0)) -def test_call_insights_client(mock_popen): +def test_reboot(mock_popen): reboot_system() - mock_popen.assert_called_once_with(["shutdown", "-r", "1"], wait=False) + mock_popen.assert_called_once_with(["/usr/sbin/shutdown", "-r", "1"], wait=False) diff --git a/tests/upgrade/test_schema.py b/tests/upgrade/test_schema.py index b5c36c9..142bb04 100644 --- a/tests/upgrade/test_schema.py +++ b/tests/upgrade/test_schema.py @@ -13,6 +13,8 @@ def test_output_schema(): schema_json = json.load(schema) # If some difference between generated json and its schema invoke exception jsonschema.validate(instance=empty_output, schema=schema_json) + assert not empty_output["alert"] + assert not empty_output["error"] def test_output_schema_entries_report(): diff --git a/tests/upgrade/test_setup_leapp.py b/tests/upgrade/test_setup_leapp.py index 9149c95..2f3a8cb 100644 --- a/tests/upgrade/test_setup_leapp.py +++ b/tests/upgrade/test_setup_leapp.py @@ -4,33 +4,48 @@ @patch("scripts.leapp_upgrade.run_subprocess") -@patch("scripts.leapp_upgrade.check_if_package_installed") +@patch("scripts.leapp_upgrade._check_if_package_installed") def test_setup_leapp_success(mock_check_if_package_installed, mock_run_subprocess): mock_run_subprocess.return_value = ("Installation successful", 0) mock_check_if_package_installed.return_value = True - command = ["dnf", "install", "leapp-upgrade", "-y"] - rhui_pkgs = [{"src_pkg": "rh-amazon-rhui-client"}, {"src_pkg": "rhui-azure-rhel8"}] + rhel7_command = [ + "/usr/bin/yum", + "install", + "leapp-upgrade", + "-y", + "--enablerepo=rhel-7-server-extras-rpms", + ] + rhel8_command = ["/usr/bin/dnf", "install", "leapp-upgrade", "-y"] - result = setup_leapp(command, rhui_pkgs) - - assert mock_check_if_package_installed.call_count == 2 - mock_run_subprocess.assert_called_once_with(command) - assert all(pkg.get("installed", False) for pkg in result) + for version, command in [("7", rhel7_command), ("8", rhel8_command)]: + result = setup_leapp(version) + mock_run_subprocess.assert_called_with(command) + assert all(pkg.get("installed", False) for pkg in result) @patch("scripts.leapp_upgrade.run_subprocess") -@patch("scripts.leapp_upgrade.check_if_package_installed") +@patch("scripts.leapp_upgrade._check_if_package_installed") def test_setup_leapp_failure(mock_check_if_package_installed, mock_run_subprocess): mock_run_subprocess.return_value = ("Installation failed", 1) mock_check_if_package_installed.return_value = True - command = ["dnf", "install", "leapp-upgrade", "-y"] - rhui_pkgs = [{"src_pkg": "rh-amazon-rhui-client"}, {"src_pkg": "rhui-azure-rhel8"}] - - with pytest.raises(ProcessError) as e_info: - setup_leapp(command, rhui_pkgs) - - mock_run_subprocess.assert_called_once_with(command) - assert mock_check_if_package_installed.call_count == 0 - assert str(e_info.value) == "Installation of leapp failed with code '1'." + rhel7_command = [ + "/usr/bin/yum", + "install", + "leapp-upgrade", + "-y", + "--enablerepo=rhel-7-server-extras-rpms", + ] + rhel8_command = ["/usr/bin/dnf", "install", "leapp-upgrade", "-y"] + + for version, command in [("7", rhel7_command), ("8", rhel8_command)]: + with pytest.raises(ProcessError) as e_info: + setup_leapp(version) + + mock_run_subprocess.assert_called_with(command) + assert mock_check_if_package_installed.call_count == 0 + assert ( + str(e_info.value) + == "Installation of leapp failed with code '1' and output: Installation failed." + ) diff --git a/tests/upgrade/test_update_insights_inventory.py b/tests/upgrade/test_update_insights_inventory.py new file mode 100644 index 0000000..7a75371 --- /dev/null +++ b/tests/upgrade/test_update_insights_inventory.py @@ -0,0 +1,18 @@ +import pytest +from mock import patch +from scripts.leapp_upgrade import ProcessError, update_insights_inventory + + +@patch("scripts.leapp_upgrade.run_subprocess", return_value=(b"", 0)) +def test_update_insights_inventory(mock_popen): + update_insights_inventory() + mock_popen.assert_called_once_with(["/usr/bin/insights-client"]) + + +@patch("scripts.leapp_upgrade.run_subprocess", return_value=(b"", 1)) +def test_update_insights_inventory_error(mock_popen): + with pytest.raises(ProcessError) as exception: + update_insights_inventory() + mock_popen.assert_called_once_with(["/usr/bin/insights-client"]) + + assert str(exception.value) == "insights-client execution exited with code '1'." From 8d3926fdb1147fdd4781478c79aa5ff4e8074a7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Zdraveck=C3=BD?= Date: Mon, 20 Nov 2023 14:06:35 +0100 Subject: [PATCH 4/5] Move reboot message to report. Move insights update inventory call, to be called only if leapp generates new report. --- scripts/leapp_upgrade.py | 19 +++++++------------ tests/upgrade/test_main.py | 2 +- tests/upgrade/test_parse_results.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/scripts/leapp_upgrade.py b/scripts/leapp_upgrade.py index 15412da..ede17b7 100644 --- a/scripts/leapp_upgrade.py +++ b/scripts/leapp_upgrade.py @@ -310,7 +310,7 @@ def _find_highest_report_level(entries): return STATUS_CODE_NAME_MAP[valid_action_levels[0]] -def parse_results(output): +def parse_results(output, leapp_upgrade_output=None): print("Processing upgrade results ...") report_json = "Not found" @@ -332,6 +332,8 @@ def parse_results(output): inhibitor_count, len(report_entries), ) + if leapp_upgrade_output and REBOOT_GUIDANCE_MESSAGE in leapp_upgrade_output: + message += " System is ready to be upgraded. Rebooting system in 1 minute." alert = inhibitor_count > 0 status = ( _find_highest_report_level(report_entries) @@ -374,7 +376,6 @@ def reboot_system(): def main(): - leapp_upgrade_output = None try: # Exit if not RHEL 7.9 or 8.4 dist, version = get_rhel_version() @@ -396,8 +397,11 @@ def main(): remove_previous_reports() leapp_upgrade_output = execute_upgrade(upgrade_command) + parse_results(output, leapp_upgrade_output) + update_insights_inventory() print("Leapp upgrade command successfully executed.") - parse_results(output) + if leapp_upgrade_output and REBOOT_GUIDANCE_MESSAGE in leapp_upgrade_output: + reboot_system() except ProcessError as exception: print(exception.report) output = OutputCollector( @@ -407,7 +411,6 @@ def main(): message=exception.message, report=exception.report, ) - leapp_upgrade_output = None except Exception as exception: print(str(exception)) output = OutputCollector( @@ -417,18 +420,10 @@ def main(): message="An unexpected error occurred. Expand the row for more details.", report=str(exception), ) - leapp_upgrade_output = None finally: print("### JSON START ###") print(json.dumps(output.to_dict(), indent=4)) print("### JSON END ###") - update_insights_inventory() - - if leapp_upgrade_output and REBOOT_GUIDANCE_MESSAGE in leapp_upgrade_output: - print("System is ready to be upgraded. Reboot is required.") - reboot_system() - else: - print("System is NOT ready to be upgraded.") if __name__ == "__main__": diff --git a/tests/upgrade/test_main.py b/tests/upgrade/test_main.py index ff4c406..4969393 100644 --- a/tests/upgrade/test_main.py +++ b/tests/upgrade/test_main.py @@ -28,7 +28,7 @@ def test_main_non_eligible_release( mock_is_non_eligible_releases.assert_called_once() mock_output_collector.assert_called_once() mock_setup_leapp.assert_not_called() - mock_update_insights_inventory.assert_called_once() + mock_update_insights_inventory.assert_not_called() @patch("scripts.leapp_upgrade.parse_results") diff --git a/tests/upgrade/test_parse_results.py b/tests/upgrade/test_parse_results.py index ca379fc..d1ecf6f 100644 --- a/tests/upgrade/test_parse_results.py +++ b/tests/upgrade/test_parse_results.py @@ -3,6 +3,7 @@ from scripts.leapp_upgrade import ( parse_results, OutputCollector, + REBOOT_GUIDANCE_MESSAGE, ) @@ -28,6 +29,33 @@ def test_gather_report_files_exist(mock_find_level, mock_exists): assert output.message == "Your system has 0 inhibitors out of 0 potential problems." +@patch("os.path.exists", return_value=True) +@patch("scripts.leapp_upgrade._find_highest_report_level", return_value="ERROR") +def test_gather_report_files_exist_with_reboot(mock_find_level, mock_exists): + test_txt_content = "Test data" + test_json_content = '{"test": "hi"}' + output = OutputCollector() + leapp_upgrade_output = ( + "LOREM IPSUM\nTEST" + REBOOT_GUIDANCE_MESSAGE + "TEST\nDOLOR SIT AMET" + ) + with patch("__builtin__.open") as mock_open_reports: + return_values = [test_json_content, test_txt_content] + mock_open_reports.side_effect = lambda file, mode: mock_open( + read_data=return_values.pop(0) + )(file, mode) + parse_results(output, leapp_upgrade_output) + + assert mock_find_level.call_count == 0 # entries do not exists -> [] + assert output.status == "SUCCESS" + assert mock_exists.call_count == 2 + assert output.report == test_txt_content + assert output.report_json.get("test") == "hi" + assert ( + output.message + == "Your system has 0 inhibitors out of 0 potential problems. System is ready to be upgraded. Rebooting system in 1 minute." + ) + + @patch("os.path.exists", return_value=False) def test_gather_report_files_not_exist(mock_exists): output = OutputCollector() From d6442d04f898c6f3bf2365bde6fd0e9ac93742a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Zdraveck=C3=BD?= Date: Tue, 21 Nov 2023 13:36:39 +0100 Subject: [PATCH 5/5] Fix tests and feedback changes --- schemas/preupgrade_schema_1.0.json | 1 - schemas/upgrade_schema_1.0.json | 69 +++++++++++++++++++++++++++ scripts/leapp_upgrade.py | 9 ++-- tests/upgrade/test_parse_results.py | 7 +-- tests/upgrade/test_run.py | 2 +- tests/upgrade/test_schema.py | 4 +- tests/upgrade/test_should_use_rhsm.py | 6 +-- 7 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 schemas/upgrade_schema_1.0.json diff --git a/schemas/preupgrade_schema_1.0.json b/schemas/preupgrade_schema_1.0.json index 86bf6a9..8096710 100644 --- a/schemas/preupgrade_schema_1.0.json +++ b/schemas/preupgrade_schema_1.0.json @@ -67,4 +67,3 @@ } } } -} diff --git a/schemas/upgrade_schema_1.0.json b/schemas/upgrade_schema_1.0.json new file mode 100644 index 0000000..8096710 --- /dev/null +++ b/schemas/upgrade_schema_1.0.json @@ -0,0 +1,69 @@ +{ + "title": "Leapp pre-upgrade script schema", + "description": "Script is expected to set up Leapp and run pre-upgrade analysis. This schema defines the output format that is expected by Red Hat Insights Task UI.", + "type": "object", + "properties": { + "alert": { + "type": "boolean" + }, + "error": { + "type": "boolean" + }, + "status": { + "$ref": "#/$defs/status_codes" + }, + "report": { + "type": "string" + }, + "message": { + "type": "string" + }, + "report_json": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "properties": { + "entries": { + "type": "object" + }, + "tasks_format_version": { + "type": "string" + }, + "tasks_format_id": { + "type": "string" + } + }, + "required": [ + "entries", + "tasks_format_version", + "tasks_format_id" + ] + } + ] + } + }, + "required": [ + "alert", + "error", + "status", + "message", + "report", + "report_json" + ], + "additionalProperties": false, + "$defs": { + "status_codes": { + "description": "The severity of the results and messages", + "type": "string", + "enum": [ + "SUCCESS", + "INFO", + "WARNING", + "ERROR" + ] + } + } + } diff --git a/scripts/leapp_upgrade.py b/scripts/leapp_upgrade.py index ede17b7..fe2dbd7 100644 --- a/scripts/leapp_upgrade.py +++ b/scripts/leapp_upgrade.py @@ -310,7 +310,7 @@ def _find_highest_report_level(entries): return STATUS_CODE_NAME_MAP[valid_action_levels[0]] -def parse_results(output, leapp_upgrade_output=None): +def parse_results(output, reboot_required=False): print("Processing upgrade results ...") report_json = "Not found" @@ -332,7 +332,7 @@ def parse_results(output, leapp_upgrade_output=None): inhibitor_count, len(report_entries), ) - if leapp_upgrade_output and REBOOT_GUIDANCE_MESSAGE in leapp_upgrade_output: + if reboot_required: message += " System is ready to be upgraded. Rebooting system in 1 minute." alert = inhibitor_count > 0 status = ( @@ -397,10 +397,11 @@ def main(): remove_previous_reports() leapp_upgrade_output = execute_upgrade(upgrade_command) - parse_results(output, leapp_upgrade_output) + reboot_required = REBOOT_GUIDANCE_MESSAGE in leapp_upgrade_output + parse_results(output, reboot_required) update_insights_inventory() print("Leapp upgrade command successfully executed.") - if leapp_upgrade_output and REBOOT_GUIDANCE_MESSAGE in leapp_upgrade_output: + if reboot_required: reboot_system() except ProcessError as exception: print(exception.report) diff --git a/tests/upgrade/test_parse_results.py b/tests/upgrade/test_parse_results.py index d1ecf6f..07d2d3b 100644 --- a/tests/upgrade/test_parse_results.py +++ b/tests/upgrade/test_parse_results.py @@ -3,7 +3,6 @@ from scripts.leapp_upgrade import ( parse_results, OutputCollector, - REBOOT_GUIDANCE_MESSAGE, ) @@ -35,15 +34,13 @@ def test_gather_report_files_exist_with_reboot(mock_find_level, mock_exists): test_txt_content = "Test data" test_json_content = '{"test": "hi"}' output = OutputCollector() - leapp_upgrade_output = ( - "LOREM IPSUM\nTEST" + REBOOT_GUIDANCE_MESSAGE + "TEST\nDOLOR SIT AMET" - ) + reboot_required = True with patch("__builtin__.open") as mock_open_reports: return_values = [test_json_content, test_txt_content] mock_open_reports.side_effect = lambda file, mode: mock_open( read_data=return_values.pop(0) )(file, mode) - parse_results(output, leapp_upgrade_output) + parse_results(output, reboot_required) assert mock_find_level.call_count == 0 # entries do not exists -> [] assert output.status == "SUCCESS" diff --git a/tests/upgrade/test_run.py b/tests/upgrade/test_run.py index 2fa1835..f1a0c6b 100644 --- a/tests/upgrade/test_run.py +++ b/tests/upgrade/test_run.py @@ -4,7 +4,7 @@ @patch("scripts.leapp_upgrade.run_subprocess", return_value=(b"", 0)) -def test_run_leapp_preupgrade(mock_popen): +def test_run_leapp_upgrade(mock_popen): execute_upgrade(["fake command"]) mock_popen.assert_called_once_with(["fake command"]) diff --git a/tests/upgrade/test_schema.py b/tests/upgrade/test_schema.py index 142bb04..cfa2117 100644 --- a/tests/upgrade/test_schema.py +++ b/tests/upgrade/test_schema.py @@ -9,7 +9,7 @@ def test_output_schema(): output_collector = OutputCollector(status=status) empty_output = output_collector.to_dict() - with open("schemas/preupgrade_schema_1.0.json", "r") as schema: + with open("schemas/upgrade_schema_1.0.json", "r") as schema: schema_json = json.load(schema) # If some difference between generated json and its schema invoke exception jsonschema.validate(instance=empty_output, schema=schema_json) @@ -28,7 +28,7 @@ def test_output_schema_entries_report(): output_collector.entries = {"hi": "world"} full_output = output_collector.to_dict() - with open("schemas/preupgrade_schema_1.0.json", "r") as schema: + with open("schemas/upgrade_schema_1.0.json", "r") as schema: schema_json = json.load(schema) # If some difference between generated json and its schema invoke exception jsonschema.validate(instance=full_output, schema=schema_json) diff --git a/tests/upgrade/test_should_use_rhsm.py b/tests/upgrade/test_should_use_rhsm.py index 69f9130..2d3d9f4 100644 --- a/tests/upgrade/test_should_use_rhsm.py +++ b/tests/upgrade/test_should_use_rhsm.py @@ -12,7 +12,7 @@ def test_should_use_no_rhsm_rhsm_and_rhui_installed( ] rhui_installed = True - command = ["preupgrade"] + command = ["upgrade"] result = should_use_no_rhsm_check(rhui_installed, command) mock_run_subprocess.call_count = 2 @@ -29,7 +29,7 @@ def test_should_use_no_rhsm_rhsm_installed_rhui_not( ] rhui_installed = False - command = ["preupgrade"] + command = ["upgrade"] result = should_use_no_rhsm_check(rhui_installed, command) mock_run_subprocess.call_count = 2 @@ -43,7 +43,7 @@ def test_should_use_no_rhsm_rhsm_not_installed(mock_run_subprocess): ] rhui_installed = True - command = ["preupgrade"] + command = ["upgrade"] result = should_use_no_rhsm_check(rhui_installed, command) mock_run_subprocess.assert_called_once()