deb-mistral/mistral/engine/base.py
Renat Akhmerov e2c89f777d Refactoring workflow handler
* Introduced new class Workflow that manages life-cycle of running
  workflows and is responsible for managing workflow persistent state
* Moved all workflow level logic to workflow handler and Workflow class
* Changed semantics if how workflows start errors are handled.
  Previously, in case of invalid user input Mistral engine would store
  information about error in "state_info" field of workflow execution
  and bubble up an exception to the user. This approach was incorrect
  for a number of reasons including broken semantics: if an exception
  was raised due to invalid input it's normal to expect that system
  state has not changed. After this refactoring, engine only raises
  an exception in case of bad user input. That way behavior is
  consistent with the idea of exceptional situations.
* Fixed unit tests in according to the previous point
* Fixed a number of logical issues in tests. For example, in
  test_default_engine.py we expected one type of errors (e.g. env not
  found) but effectively received another one (invalid input).

Partially implements: blueprint mistral-engine-error-handling

Change-Id: I09070411fd833df8284cb80db69b8401a40eb6fe
2016-06-07 18:38:38 +07:00

186 lines
5.9 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright 2014 - Mirantis, Inc.
#
# 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 abc
import jsonschema
import six
from mistral import exceptions as exc
from mistral.utils import inspect_utils
from mistral.workflow import data_flow
@six.add_metaclass(abc.ABCMeta)
class Engine(object):
"""Engine interface."""
@abc.abstractmethod
def start_workflow(self, wf_identifier, wf_input, description='',
**params):
"""Starts the specified workflow.
:param wf_identifier: Workflow ID or name. Workflow ID is recommended,
workflow name will be deprecated since Mitaka.
:param wf_input: Workflow input data as a dictionary.
:param description: Execution description.
:param params: Additional workflow type specific parameters.
:return: Workflow execution object.
"""
raise NotImplementedError
@abc.abstractmethod
def start_action(self, action_name, action_input,
description=None, **params):
"""Starts the specific action.
:param action_name: Action name.
:param action_input: Action input data as a dictionary.
:param description: Execution description.
:param params: Additional options for action running.
:return: Action execution object.
"""
raise NotImplementedError
@abc.abstractmethod
def on_action_complete(self, action_ex_id, result):
"""Accepts action result and continues the workflow.
Action execution result here is a result which comes from an
action/workflow associated which the task.
:param action_ex_id: Action execution id.
:param result: Action/workflow result. Instance of
mistral.workflow.base.Result
:return:
"""
raise NotImplementedError
@abc.abstractmethod
def pause_workflow(self, wf_ex_id):
"""Pauses workflow.
:param wf_ex_id: Execution id.
:return: Workflow execution object.
"""
raise NotImplementedError
@abc.abstractmethod
def resume_workflow(self, wf_ex_id, env=None):
"""Resumes workflow.
:param wf_ex_id: Execution id.
:param env: Workflow environment.
:return: Workflow execution object.
"""
raise NotImplementedError
@abc.abstractmethod
def rerun_workflow(self, task_ex_id, reset=True, env=None):
"""Rerun workflow from the specified task.
:param task_ex_id: Task execution id.
:param reset: If True, reset task state including deleting its action
executions.
:param env: Workflow environment.
:return: Workflow execution object.
"""
raise NotImplementedError
@abc.abstractmethod
def stop_workflow(self, wf_ex_id, state, message):
"""Stops workflow.
:param wf_ex_id: Workflow execution id.
:param state: State assigned to the workflow. Permitted states are
SUCCESS or ERROR.
:param message: Optional information string.
:return: Workflow execution.
"""
raise NotImplementedError
@abc.abstractmethod
def rollback_workflow(self, wf_ex_id):
"""Rolls back workflow execution.
:param wf_ex_id: Execution id.
:return: Workflow execution object.
"""
raise NotImplementedError
@six.add_metaclass(abc.ABCMeta)
class Executor(object):
"""Action executor interface."""
@abc.abstractmethod
def run_action(self, action_ex_id, action_class_str, attributes,
action_params):
"""Runs action.
:param action_ex_id: Corresponding action execution id.
:param action_class_str: Path to action class in dot notation.
:param attributes: Attributes of action class which will be set to.
:param action_params: Action parameters.
"""
raise NotImplementedError()
@six.add_metaclass(abc.ABCMeta)
class TaskPolicy(object):
"""Task policy.
Provides interface to perform any work after a task has completed.
An example of task policy may be 'retry' policy that makes engine
to run a task repeatedly if it finishes with a failure.
"""
_schema = {}
def before_task_start(self, task_ex, task_spec):
"""Called right before task start.
:param task_ex: DB model for task that is about to start.
:param task_spec: Task specification.
"""
# No-op by default.
data_flow.evaluate_object_fields(self, task_ex.in_context)
self._validate()
def after_task_complete(self, task_ex, task_spec):
"""Called right after task completes.
:param task_ex: Completed task DB model.
:param task_spec: Completed task specification.
"""
# No-op by default.
data_flow.evaluate_object_fields(self, task_ex.in_context)
self._validate()
def _validate(self):
"""Validation of types after YAQL evaluation."""
props = inspect_utils.get_public_fields(self)
try:
jsonschema.validate(props, self._schema)
except Exception as e:
raise exc.InvalidModelException(
"Invalid data type in %s: %s. Value(s) can be shown after "
"YAQL evaluating. If you use YAQL here, please correct it."
% (self.__class__.__name__, e.message)
)