Skip to content

Commit

Permalink
[RHELC-1342] Add post conversion stages for Action Framework (#1144)
Browse files Browse the repository at this point in the history
* Add post conversion stages for Action Framework

In this commit, we are introducing two new stages for enabling the post
conversion in the action framework.

We are also introducing an environment variable to not break the current
workflow and let the conversion function as-is for now. Anyone that is
willing to test and run the post conversion actions will need to use
this environment var.

* Add unit tests

* Apply suggestions from code review

Co-authored-by: Adam Hosek <hosek.adam@outlook.com>

* Update convert2rhel/main.py

Co-authored-by: Adam Hosek <hosek.adam@outlook.com>

---------

Co-authored-by: Adam Hosek <hosek.adam@outlook.com>
  • Loading branch information
r0x0d and hosekadam authored Mar 22, 2024
1 parent 061bc66 commit 9894193
Show file tree
Hide file tree
Showing 6 changed files with 1,119 additions and 190 deletions.
89 changes: 66 additions & 23 deletions convert2rhel/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,42 @@ def resolve_action_order(potential_actions, previously_resolved_actions=None):
)


def run_actions():
def parse_action_results(results):
"""
Parse and format action results
.. note::
# Format results as a dictionary:
{
"$Action_id": {
"messages": [
{
"level": int,
"id": "$id",
"message": "" or "$message"
},
]
"result": {
"level": int,
"id": "$id",
"message": "" or "$message"
},
},
}
:param results: Unformatted results given by the actions
:type results: dict
:return: Formatted dictionary with the results
:rtype: dict[str, dict[str, list | dict]]
"""
formatted_results = {}
for action in itertools.chain(*results):
msgs = [msg.to_dict() for msg in action.messages]
formatted_results[action.id] = {"messages": msgs, "result": action.result.to_dict()}
return formatted_results


def run_pre_actions():
"""
Run all of the pre-ponr Actions.
Expand Down Expand Up @@ -719,28 +754,36 @@ def run_actions():
# Run the Actions in system_checks and all subsequent Stages.
results = system_checks.run()

# Format results as a dictionary:
# {
# "$Action_id": {
# "messages": [
# {
# "level": int,
# "id": "$id",
# "message": "" or "$message"
# },
# ]
# "result": {
# "level": int,
# "id": "$id",
# "message": "" or "$message"
# },
# },
# }
formatted_results = {}
for action in itertools.chain(*results):
msgs = [msg.to_dict() for msg in action.messages]
formatted_results[action.id] = {"messages": msgs, "result": action.result.to_dict()}
return formatted_results
return parse_action_results(results)


def run_post_actions():
"""
This function runs the Actions that occur after the Point of no Return.
"""
# Stages are created in the opposite order that they are run in so that
# each Stage can know about the Stage that comes after it (via the
# next_stage parameter).
#
# When we call check_dependencies() or run() on the first Stage
# (conversion), it will operate on the first Stage and then recursively
# call check_dependencies() or run() on the next_stage.
post_conversion = Stage("post_conversion", "Final modifications to the system")
conversion = Stage("conversion", "Starting Conversion", next_stage=post_conversion)

try:
# Check dependencies are satisfied for conversion and all subsequent
# Stages.
conversion.check_dependencies()
except DependencyError as e:
# We want to fail early if dependencies are not properly set. This
# way we should fail in testing before release.
logger.critical("Some dependencies were set on Actions but not present in convert2rhel: %s" % e)

# Run the Actions in conversion and all subsequent Stages.
results = conversion.run()

return parse_action_results(results)


def level_for_raw_action_data(message):
Expand Down
Empty file.
Empty file.
45 changes: 34 additions & 11 deletions convert2rhel/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from convert2rhel import actions, applock, backup, breadcrumbs, checks, exceptions, grub, hostmetering
from convert2rhel import logger as logger_module
from convert2rhel import pkghandler, pkgmanager, redhatrelease, repo, subscription, systeminfo, toolopts, utils
from convert2rhel import pkghandler, pkgmanager, redhatrelease, subscription, systeminfo, toolopts, utils
from convert2rhel.actions import level_for_raw_action_data, report


Expand Down Expand Up @@ -96,6 +96,7 @@ def main_locked():
"""Perform all steps for the entire conversion process."""

pre_conversion_results = None
post_conversion_results = None
process_phase = ConversionPhase.POST_CLI

# since we now have root, we can add the FileLogging
Expand All @@ -110,25 +111,20 @@ def main_locked():

# Note: set pre_conversion_results before changing to the next phase so
# we don't fail in case rollback is triggered during
# actions.run_actions() (either from a bug or from the user hitting
# actions.run_pre_actions() (either from a bug or from the user hitting
# Ctrl-C)
process_phase = ConversionPhase.PRE_PONR_CHANGES
pre_conversion_results = actions.run_actions()
pre_conversion_results = actions.run_pre_actions()

if toolopts.tool_opts.activity == "analysis":
process_phase = ConversionPhase.ANALYZE_EXIT
raise _AnalyzeExit()

pre_conversion_failures = actions.find_actions_of_severity(
pre_conversion_results, "SKIP", level_for_raw_action_data
)
if pre_conversion_failures:
# The report will be handled in the error handler, after rollback.
loggerinst.critical("Conversion failed.")
_raise_for_skipped_failures(pre_conversion_results)

# Print the assessment just before we ask the user whether to continue past the PONR
report.summary(
pre_conversion_results,
results=pre_conversion_results,
include_all_reports=False,
disable_colors=logger_module.should_disable_color_output(),
)
Expand All @@ -144,7 +140,20 @@ def main_locked():
utils.ask_to_continue()

process_phase = ConversionPhase.POST_PONR_CHANGES
post_ponr_changes()
# TODO(r0x0d): Just temporary, so we don't need to rush with migrating
# all the functions at once to the framework.
if "CONVERT2RHEL_EXPERIMENTAL_POST_PONR_ACTIONS" in os.environ:
post_conversion_results = actions.run_post_actions()
_raise_for_skipped_failures(post_conversion_results)
# Print the assessment just before we ask the user whether to continue past the PONR
report.summary(
results=post_conversion_results,
include_all_reports=False,
disable_colors=logger_module.should_disable_color_output(),
)
else:
post_ponr_changes()

loggerinst.info("\nConversion successful!\n")

# restart system if required
Expand Down Expand Up @@ -183,6 +192,20 @@ def main_locked():
return 0


def _raise_for_skipped_failures(results):
"""Analyze the action results for failures
:param results: The action results from the framework
:type results: dict
:raises SystemExit: In case we detect any actions that has level of `SKIP`
or above.
"""
failures = actions.find_actions_of_severity(results, "SKIP", level_for_raw_action_data)
if failures:
# The report will be handled in the error handler, after rollback.
loggerinst.critical("Conversion failed.")


def _handle_main_exceptions(process_phase, pre_conversion_results=None):
"""Common steps to handle graceful exit due to several different Exception types."""
breadcrumbs.breadcrumbs.finish_collection()
Expand Down
Loading

0 comments on commit 9894193

Please sign in to comment.