To complement the future changes in patterns we also want to allow the execution of patterns to be affected in a similar manner so that symbol lookup is no longer as confined as it was. This change adds in the following: - Symbol lookup by walking through an atoms contained scope/s. - Better error messaging when symbols are not found. - Adjusted & new tests (existing ones work). - Better logging of the symbol lookup mechanism (helpful during debugging, although it is very verbose...) Part of blueprint taskflow-improved-scoping Change-Id: Id921a4abd9bf2b7b5c5a762337f8e90e8f1fe194
122 lines
5.1 KiB
Python
122 lines
5.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (C) 2012-2013 Yahoo! Inc. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import logging
|
|
|
|
from taskflow import states
|
|
from taskflow.utils import misc
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
SAVE_RESULT_STATES = (states.SUCCESS, states.FAILURE)
|
|
|
|
|
|
class TaskAction(object):
|
|
|
|
def __init__(self, storage, task_executor, notifier, walker_factory):
|
|
self._storage = storage
|
|
self._task_executor = task_executor
|
|
self._notifier = notifier
|
|
self._walker_factory = walker_factory
|
|
|
|
def _is_identity_transition(self, state, task, progress):
|
|
if state in SAVE_RESULT_STATES:
|
|
# saving result is never identity transition
|
|
return False
|
|
old_state = self._storage.get_atom_state(task.name)
|
|
if state != old_state:
|
|
# changing state is not identity transition by definition
|
|
return False
|
|
# NOTE(imelnikov): last thing to check is that the progress has
|
|
# changed, which means progress is not None and is different from
|
|
# what is stored in the database.
|
|
if progress is None:
|
|
return False
|
|
old_progress = self._storage.get_task_progress(task.name)
|
|
if old_progress != progress:
|
|
return False
|
|
return True
|
|
|
|
def change_state(self, task, state, result=None, progress=None):
|
|
if self._is_identity_transition(state, task, progress):
|
|
# NOTE(imelnikov): ignore identity transitions in order
|
|
# to avoid extra write to storage backend and, what's
|
|
# more important, extra notifications
|
|
return
|
|
if state in SAVE_RESULT_STATES:
|
|
self._storage.save(task.name, result, state)
|
|
else:
|
|
self._storage.set_atom_state(task.name, state)
|
|
if progress is not None:
|
|
self._storage.set_task_progress(task.name, progress)
|
|
task_uuid = self._storage.get_atom_uuid(task.name)
|
|
details = dict(task_name=task.name,
|
|
task_uuid=task_uuid,
|
|
result=result)
|
|
self._notifier.notify(state, details)
|
|
if progress is not None:
|
|
task.update_progress(progress)
|
|
|
|
def _on_update_progress(self, task, event_data, progress, **kwargs):
|
|
"""Should be called when task updates its progress."""
|
|
try:
|
|
self._storage.set_task_progress(task.name, progress, kwargs)
|
|
except Exception:
|
|
# Update progress callbacks should never fail, so capture and log
|
|
# the emitted exception instead of raising it.
|
|
LOG.exception("Failed setting task progress for %s to %0.3f",
|
|
task, progress)
|
|
|
|
def schedule_execution(self, task):
|
|
self.change_state(task, states.RUNNING, progress=0.0)
|
|
scope_walker = self._walker_factory(task)
|
|
kwargs = self._storage.fetch_mapped_args(task.rebind,
|
|
atom_name=task.name,
|
|
scope_walker=scope_walker)
|
|
task_uuid = self._storage.get_atom_uuid(task.name)
|
|
return self._task_executor.execute_task(task, task_uuid, kwargs,
|
|
self._on_update_progress)
|
|
|
|
def complete_execution(self, task, result):
|
|
if isinstance(result, misc.Failure):
|
|
self.change_state(task, states.FAILURE, result=result)
|
|
else:
|
|
self.change_state(task, states.SUCCESS,
|
|
result=result, progress=1.0)
|
|
|
|
def schedule_reversion(self, task):
|
|
self.change_state(task, states.REVERTING, progress=0.0)
|
|
scope_walker = self._walker_factory(task)
|
|
kwargs = self._storage.fetch_mapped_args(task.rebind,
|
|
atom_name=task.name,
|
|
scope_walker=scope_walker)
|
|
task_uuid = self._storage.get_atom_uuid(task.name)
|
|
task_result = self._storage.get(task.name)
|
|
failures = self._storage.get_failures()
|
|
future = self._task_executor.revert_task(task, task_uuid, kwargs,
|
|
task_result, failures,
|
|
self._on_update_progress)
|
|
return future
|
|
|
|
def complete_reversion(self, task, rev_result):
|
|
if isinstance(rev_result, misc.Failure):
|
|
self.change_state(task, states.FAILURE)
|
|
else:
|
|
self.change_state(task, states.REVERTED, progress=1.0)
|
|
|
|
def wait_for_any(self, fs, timeout):
|
|
return self._task_executor.wait_for_any(fs, timeout)
|