# DBus Task interface. # # Base class of tasks. # Task is used by modules to implement asynchronous time consuming installation # or configuration tasks. # # Copyright (C) 2017 Red Hat, Inc. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of # the GNU General Public License v.2, or (at your option) any later version. # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY expressed or implied, including the implied warranties of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. You should have received a copy of the # GNU General Public License along with this program; if not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. Any Red Hat trademarks that are incorporated in the # source code or documentation are not subject to the GNU General Public # License and may only be used or replicated with the express permission of # Red Hat, Inc. # import traceback from abc import abstractmethod from pyanaconda.core.constants import THREAD_DBUS_TASK from dasbus.server.publishable import Publishable from pyanaconda.modules.common.errors.task import NoResultError from pyanaconda.modules.common.task.task_interface import TaskInterface, ValidationTaskInterface from pyanaconda.modules.common.task.cancellable import Cancellable from pyanaconda.modules.common.task.progress import ProgressReporter from pyanaconda.modules.common.task.result import ResultProvider from pyanaconda.modules.common.task.runnable import Runnable from pyanaconda.core.threads import thread_manager from pyanaconda.anaconda_loggers import get_module_logger log = get_module_logger(__name__) __all__ = ['AbstractTask', 'Task', 'ValidationTask'] class AbstractTask(Runnable, Cancellable, Publishable, ProgressReporter, ResultProvider): """Abstract class for running a long-term task.""" @property @abstractmethod def name(self): """Name of this task. For example: "Install the payload" :returns: string with the task name """ return "" def for_publication(self): """Return a DBus representation.""" return TaskInterface(self) class Task(AbstractTask): """Abstract class for running a long-term task in a thread.""" _thread_counter = 0 def __init__(self): super().__init__() self._thread_name = self._generate_thread_name() @property def steps(self): """Total number of steps.""" return 1 @property def is_running(self): """Is the task running.""" return thread_manager.exists(self._thread_name) def start(self): """Start the task in a new thread.""" thread_manager.add_thread( name=self._thread_name, target=self._thread_run_callback, target_started=self._task_started_callback, target_stopped=self._task_stopped_callback, target_failed=self._thread_failed_callback, fatal=False ) def _thread_run_callback(self): """Run a task and report the success.""" self._task_run_callback() self._task_succeeded_callback() def _task_run_callback(self): """Report the first step and run the task. . Don't run the task if the task was canceled. """ if self.check_cancel(): log.info("'%s' is canceled.", self.name) return log.info(self.name) self._set_result(self.run()) def _task_succeeded_callback(self): """Callback for a successful task. Don't report the success if the task was canceled. """ if not self.check_cancel(): super()._task_succeeded_callback() def _thread_failed_callback(self, *exc_info): """Log the error and report the failure.""" # pylint: disable=no-value-for-parameter formatted_info = "".join(traceback.format_exception(*exc_info)) log.error("Thread %s has failed: %s", self._thread_name, formatted_info) self._task_failed_callback() def run_with_signals(self): """Run the task in the current thread with enabled signals. Call this method to run the task synchronously in the current thread. It will emit all signals in the same order as the start method. :raise: an error if the task fails :return: a result of the task if the task succeeds """ try: self._task_started_callback() self._task_run_callback() except Exception: # pylint: disable=broad-except self._task_failed_callback() raise else: self._task_succeeded_callback() finally: self._task_stopped_callback() try: return self.get_result() except NoResultError: return None @abstractmethod def run(self): """The task implementation. Report the progress of the task with the self.report_progress method. Call self.check_cancel to check if the task should be canceled and terminate the task immediately if it returns True. Return a result of the task or None if the task doesn't provide a result. :return: a result of the task """ return None def finish(self): """Finish the task run. Call this method after the task has stopped. If there was raised an exception during the task run, it will be raised here again. """ thread_manager.raise_if_error(self._thread_name) @classmethod def _generate_thread_name(cls): """Generate the name of the thread.""" cls._thread_counter += 1 return "{}-{}-{}".format( THREAD_DBUS_TASK, cls.__name__, cls._thread_counter ) class ValidationTask(Task): """Abstract class for running a validation task.""" def for_publication(self): """Return a DBus representation.""" return ValidationTaskInterface(self) @abstractmethod def run(self): """The validation implementation. Run the validation and return an validation report with error and warning messages. :return: an instance of ValidationReport """ return None