From cf30d38ef0a0f614a891a3a4e5049c6482d35c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ka=C5=88kovsk=C3=BD?= Date: Thu, 16 Nov 2023 10:58:13 +0100 Subject: [PATCH] Creating categories dbus API for installation phases --- pyanaconda/core/constants.py | 8 +++ pyanaconda/installation.py | 63 +++++++++++++------ pyanaconda/installation_tasks.py | 17 ++++- .../modules/common/constants/interfaces.py | 5 ++ pyanaconda/modules/common/task/progress.py | 20 ++++++ .../modules/common/task/task_interface.py | 22 ++++++- .../installation/InstallationProgress.jsx | 24 +++---- 7 files changed, 119 insertions(+), 40 deletions(-) diff --git a/pyanaconda/core/constants.py b/pyanaconda/core/constants.py index b9861ea4f9b3..ec87f07e5b45 100644 --- a/pyanaconda/core/constants.py +++ b/pyanaconda/core/constants.py @@ -508,3 +508,11 @@ class DisplayModes(Enum): # FIPS mode minimum LUKS passphrase length FIPS_PASSPHRASE_MIN_LENGTH = 8 + + +# Installation categories +class InstallationCategories(Enum): + STORAGE = 0 + PAYLOAD = 1 + BOOTLOADER = 2 + CONFIGURATION = 3 diff --git a/pyanaconda/installation.py b/pyanaconda/installation.py index 117301839593..003268294f80 100644 --- a/pyanaconda/installation.py +++ b/pyanaconda/installation.py @@ -19,11 +19,12 @@ # from pyanaconda.core.dbus import DBus from pyanaconda.core.configuration.anaconda import conf -from pyanaconda.core.constants import PAYLOAD_LIVE_TYPES, PAYLOAD_TYPE_DNF +from pyanaconda.core.constants import PAYLOAD_LIVE_TYPES, PAYLOAD_TYPE_DNF, InstallationCategories from pyanaconda.modules.common.constants.objects import BOOTLOADER, SNAPSHOT, FIREWALL from pyanaconda.modules.common.constants.services import STORAGE, USERS, SERVICES, NETWORK, \ SECURITY, LOCALIZATION, TIMEZONE, BOSS, SUBSCRIPTION from pyanaconda.modules.common.task import sync_run_task, Task as InstallationTask +from pyanaconda.modules.common.task.task_interface import CategoryReportTaskInterface from pyanaconda.modules.common.util import is_module_available from pyanaconda import flags from pyanaconda.core import util @@ -50,7 +51,6 @@ def _writeKS(ksdata): f.write("# Generated by Anaconda {}\n".format(util.get_anaconda_version_string())) f.write(str(ksdata)) - class RunInstallationTask(InstallationTask): """Task to run the installation queue.""" @@ -82,8 +82,13 @@ def run(self): ksdata=self._ksdata, ) + def for_publication(self): + """Return a DBus representation.""" + return CategoryReportTaskInterface(self) + def _queue_started_cb(self, task): """The installation queue was started.""" + self.report_category(task.task_category) self.report_progress(task.status_message) def _task_completed_cb(self, task): @@ -107,7 +112,8 @@ def _prepare_configuration(self, payload, ksdata): # we only run the tasks if the Subscription module is available subscription_config = TaskQueue( "Subscription configuration", - _("Configuring Red Hat subscription") + _("Configuring Red Hat subscription"), + InstallationCategories.CONFIGURATION ) subscription_proxy = SUBSCRIPTION.get_proxy() subscription_dbus_tasks = subscription_proxy.InstallWithTasks() @@ -117,7 +123,8 @@ def _prepare_configuration(self, payload, ksdata): # schedule the execute methods of ksdata that require an installed system to be present os_config = TaskQueue( "Installed system configuration", - _("Configuring installed system") + _("Configuring installed system"), + InstallationCategories.CONFIGURATION ) # add installation tasks for the Security DBus module @@ -158,7 +165,8 @@ def _prepare_configuration(self, payload, ksdata): overwrite = payload.type in PAYLOAD_LIVE_TYPES network_config = TaskQueue( "Network configuration", - _("Writing network configuration") + _("Writing network configuration"), + InstallationCategories.CONFIGURATION ) network_config.append(Task( "Network configuration", @@ -171,7 +179,8 @@ def _prepare_configuration(self, payload, ksdata): if is_module_available(USERS): user_config = TaskQueue( "User creation", - _("Creating users") + _("Creating users"), + InstallationCategories.CONFIGURATION ) users_proxy = USERS.get_proxy() users_dbus_tasks = users_proxy.InstallWithTasks() @@ -181,7 +190,8 @@ def _prepare_configuration(self, payload, ksdata): # Anaconda addon configuration addon_config = TaskQueue( "Anaconda addon configuration", - _("Configuring addons") + _("Configuring addons"), + InstallationCategories.CONFIGURATION ) boss_proxy = BOSS.get_proxy() @@ -194,7 +204,8 @@ def _prepare_configuration(self, payload, ksdata): # Initramfs generation generate_initramfs = TaskQueue( "Initramfs generation", - _("Generating initramfs") + _("Generating initramfs"), + InstallationCategories.BOOTLOADER ) bootloader_proxy = STORAGE.get_proxy(BOOTLOADER) @@ -236,7 +247,8 @@ def run_generate_initramfs(): if flags.flags.kexec: kexec_setup = TaskQueue( "Kexec setup", - _("Setting up kexec") + _("Setting up kexec"), + InstallationCategories.CONFIGURATION ) kexec_setup.append(Task( "Setup kexec", @@ -247,7 +259,8 @@ def run_generate_initramfs(): # write anaconda related configs & kickstarts write_configs = TaskQueue( "Write configs and kickstarts", - _("Storing configuration files and kickstarts") + _("Storing configuration files and kickstarts"), + InstallationCategories.CONFIGURATION ) # Write the kickstart file to the installed system (or, copy the input @@ -267,7 +280,8 @@ def run_generate_initramfs(): post_scripts = TaskQueue( "Post installation scripts", - _("Running post-installation scripts") + _("Running post-installation scripts"), + InstallationCategories.CONFIGURATION ) post_scripts.append(Task( "Run post installation scripts", @@ -315,7 +329,8 @@ def _prepare_installation(self, payload, ksdata): # setup the installation environment setup_environment = TaskQueue( "Installation environment setup", - _("Setting up the installation environment") + _("Setting up the installation environment"), + InstallationCategories.STORAGE ) boss_proxy = BOSS.get_proxy() @@ -341,7 +356,8 @@ def _prepare_installation(self, payload, ksdata): storage_proxy = STORAGE.get_proxy() early_storage = TaskQueue( "Early storage configuration", - _("Configuring storage") + _("Configuring storage"), + InstallationCategories.STORAGE ) early_storage.append_dbus_tasks(STORAGE, storage_proxy.InstallWithTasks()) @@ -354,7 +370,8 @@ def _prepare_installation(self, payload, ksdata): # Run %pre-install scripts with the filesystem mounted and no packages pre_install_scripts = TaskQueue( "Pre-install scripts", - _("Running pre-installation scripts") + _("Running pre-installation scripts"), + InstallationCategories.STORAGE ) pre_install_scripts.append(Task( "Run %pre-install scripts", @@ -367,7 +384,8 @@ def _prepare_installation(self, payload, ksdata): # - check for possibly needed additional packages. pre_install = TaskQueue( "Pre install tasks", - _("Running pre-installation tasks") + _("Running pre-installation tasks"), + InstallationCategories.STORAGE ) if is_module_available(SECURITY): @@ -391,7 +409,8 @@ def _prepare_installation(self, payload, ksdata): payload_install = TaskQueue( "Payload installation", - _("Installing the software") + _("Installing the software"), + InstallationCategories.PAYLOAD ) payload_install.append(Task( "Install the payload", @@ -403,7 +422,8 @@ def _prepare_installation(self, payload, ksdata): if payload.type != PAYLOAD_TYPE_DNF: late_storage = TaskQueue( "Late storage configuration", - _("Configuring storage") + _("Configuring storage"), + InstallationCategories.STORAGE, ) conf_task = storage_proxy.WriteConfigurationWithTask() late_storage.append_dbus_tasks(STORAGE, [conf_task]) @@ -413,7 +433,8 @@ def _prepare_installation(self, payload, ksdata): bootloader_proxy = STORAGE.get_proxy(BOOTLOADER) bootloader_install = TaskQueue( "Bootloader installation", - _("Installing boot loader") + _("Installing boot loader"), + InstallationCategories.BOOTLOADER ) def run_configure_bootloader(): @@ -446,7 +467,8 @@ def run_install_bootloader(): post_install = TaskQueue( "Post-installation setup tasks", - _("Performing post-installation setup tasks") + _("Performing post-installation setup tasks"), + InstallationCategories.CONFIGURATION ) post_install.append(Task( "Run post-installation setup tasks", @@ -460,7 +482,8 @@ def run_install_bootloader(): if snapshot_proxy.IsRequested(SNAPSHOT_WHEN_POST_INSTALL): snapshot_creation = TaskQueue( "Creating post installation snapshots", - _("Creating snapshots") + _("Creating snapshots"), + InstallationCategories.PAYLOAD ) snapshot_task = snapshot_proxy.CreateWithTask(SNAPSHOT_WHEN_POST_INSTALL) snapshot_creation.append_dbus_tasks(STORAGE, [snapshot_task]) diff --git a/pyanaconda/installation_tasks.py b/pyanaconda/installation_tasks.py index 36bb2fbafee3..affb3e3ca943 100644 --- a/pyanaconda/installation_tasks.py +++ b/pyanaconda/installation_tasks.py @@ -91,8 +91,9 @@ class TaskQueue(BaseTask): TaskQueues and Tasks can be mixed in a single TaskQueue. """ - def __init__(self, name, status_message=None): + def __init__(self, name, status_message=None, task_category=None): super().__init__(name) + self._task_category = task_category self._status_message = status_message # the list backing this TaskQueue instance self._queue = [] @@ -103,6 +104,20 @@ def __init__(self, name, status_message=None): self.task_started = Signal() self.task_completed = Signal() + @property + def task_category(self): + """A category describing the Queue is trying to achieve. + + Eq. "Converting all foo into bar." + + The current main usecase is to set the ProgressHub status message when + a TaskQueue is started. + + :returns: a task category + :rtype: str + """ + return self._task_category + @property def status_message(self): """A status message describing the Queue is trying to achieve. diff --git a/pyanaconda/modules/common/constants/interfaces.py b/pyanaconda/modules/common/constants/interfaces.py index 1b544826722f..a143b0459c4e 100644 --- a/pyanaconda/modules/common/constants/interfaces.py +++ b/pyanaconda/modules/common/constants/interfaces.py @@ -35,6 +35,11 @@ basename="Task" ) +TASK_CATEGORY = DBusInterfaceIdentifier( + namespace=ANACONDA_NAMESPACE, + basename="TaskCategory" +) + DEVICE_TREE_VIEWER = DBusInterfaceIdentifier( namespace=DEVICE_TREE_NAMESPACE, basename="Viewer" diff --git a/pyanaconda/modules/common/task/progress.py b/pyanaconda/modules/common/task/progress.py index 428678c80d54..acdfea4a36c4 100644 --- a/pyanaconda/modules/common/task/progress.py +++ b/pyanaconda/modules/common/task/progress.py @@ -30,9 +30,11 @@ class ProgressReporter(ABC): def __init__(self): super().__init__() self._progress_changed_signal = Signal() + self._category_changed_signal = Signal() self.__progress_lock = Lock() self.__progress_step = 0 + self.__progress_category = None self.__progress_msg = "" @property @@ -55,6 +57,24 @@ def progress_changed_signal(self): """Signal emits when the progress of the task changes.""" return self._progress_changed_signal + @property + def category_changed_signal(self): + """Signal emits when the category of the task changes.""" + return self._category_changed_signal + + @async_action_nowait + def report_category(self, category): + current_category = self.__progress_category + if category is None: + return + else: + category_value = category.value + if current_category is None or current_category < category_value: + self.__progress_category = category_value + current_category = category_value + self._category_changed_signal.emit(current_category) + + @async_action_nowait def report_progress(self, message, step_number=None, step_size=None): """Report a progress change. diff --git a/pyanaconda/modules/common/task/task_interface.py b/pyanaconda/modules/common/task/task_interface.py index 8a539b14d9e1..e7af43de9cde 100644 --- a/pyanaconda/modules/common/task/task_interface.py +++ b/pyanaconda/modules/common/task/task_interface.py @@ -21,13 +21,13 @@ # Red Hat, Inc. # from dasbus.server.interface import dbus_interface, dbus_signal, dbus_class -from pyanaconda.modules.common.constants.interfaces import TASK +from pyanaconda.modules.common.constants.interfaces import TASK, TASK_CATEGORY from pyanaconda.modules.common.base.base_template import InterfaceTemplate from dasbus.typing import * # pylint: disable=wildcard-import from pyanaconda.modules.common.errors.task import NoResultError from pyanaconda.modules.common.structures.validation import ValidationReport -__all__ = ['TaskInterface', 'ValidationTaskInterface'] +__all__ = ['TaskInterface', 'ValidationTaskInterface', 'CategoryReportTaskInterface'] @dbus_interface(TASK.interface_name) @@ -149,3 +149,21 @@ def convert_result(value) -> Variant: :return: a variant with the structure """ return get_variant(Structure, ValidationReport.to_structure(value)) + + +@dbus_interface(TASK_CATEGORY.interface_name) +class CategoryReportTaskInterface(TaskInterface): + "DBus interface for a task category report" + + def connect_signals(self): + super().connect_signals() + self.implementation.category_changed_signal.connect(self.CategoryChanged) + + @dbus_signal + def CategoryChanged(self, category: Int): + """Signal making progress for this task. + + :param category: Number of the category. See pyanaconda/core/constants.py + InstallationCategories for info about a category indexes. + """ + pass diff --git a/ui/webui/src/components/installation/InstallationProgress.jsx b/ui/webui/src/components/installation/InstallationProgress.jsx index 4085ef6b8b1f..3652d341e22a 100644 --- a/ui/webui/src/components/installation/InstallationProgress.jsx +++ b/ui/webui/src/components/installation/InstallationProgress.jsx @@ -60,6 +60,10 @@ export const InstallationProgress = ({ onCritFail }) => { "org.fedoraproject.Anaconda.Task", tasks[0] ); + const categoryProxy = new BossClient().client.proxy( + "org.fedoraproject.Anaconda.TaskCategory", + tasks[0] + ); const addEventListeners = () => { taskProxy.addEventListener("ProgressChanged", (_, step, message) => { @@ -69,23 +73,6 @@ export const InstallationProgress = ({ onCritFail }) => { ret => setSteps(ret.v), onCritFail() ); - // FIXME: hardcoded progress steps - // - if ProgressStepper turns out to be viable, - // use a proper DBus API for progress tep discovery - // and switching - // - // storage - } else if (step <= 3) { - setCurrentProgressStep(0); - // payload - } else if (step === 4) { - setCurrentProgressStep(1); - // configuration - } else if (step >= 5 && step <= 11) { - setCurrentProgressStep(2); - // bootloader - } else if (step >= 12) { - setCurrentProgressStep(3); } if (message) { setStatusMessage(message); @@ -104,6 +91,9 @@ export const InstallationProgress = ({ onCritFail }) => { setStatus("success"); setCurrentProgressStep(4); }); + categoryProxy.addEventListener("CategoryChanged", (_, category) => { + setCurrentProgressStep(category); + }); }; taskProxy.wait(() => { addEventListeners();