|
|
(47 intermediate revisions by 2 users not shown) |
Line 1: |
Line 1: |
− | == Overview ==
| + | '''Revised on:''' {{REVISIONMONTH1}}/{{REVISIONDAY}}/{{REVISIONYEAR}} by {{REVISIONUSER}} |
| | | |
− | In TaskFlow, all flow state should go to storage. That includes
| + | The page was moved to developers documentation: http://docs.openstack.org/developer/taskflow/arguments_and_results.html |
− | all the information that task needs when it is executed (task arguments), and
| |
− | all the information task produces (task results). Developer who implements task
| |
− | or flow can specify what arguments task accepts and what result it returns
| |
− | in several ways.
| |
− | | |
− | Set of names of task arguments is available as <code>requires</code> property
| |
− | of the task instance. When task is about to be executed values with this names
| |
− | are retrieved from storage and passed to <code>execute</code> method of the task
| |
− | as keyword arguments.
| |
− | | |
− | Set of names of task results (what task provides) is available as
| |
− | <code>provides</code> property of task instance. After task finishes
| |
− | successfully, it's result(s) (what task <code>execute</code> method returns) are
| |
− | available by these names from storage (there will be examples below).
| |
− | | |
− | == Arguments Specification ==
| |
− | | |
− | There are different way to specify task argument set.
| |
− | | |
− | === Arguments Inference ===
| |
− | | |
− | Task arguments can be inferred from arguments of <code>execute</code> method of
| |
− | the task. For example:
| |
− | | |
− | >>> class MyTask(task.Task):
| |
− | ... def execute(self, spam, eggs):
| |
− | ... return spam + eggs
| |
− | ...
| |
− | >>> MyTask().requires
| |
− | set(['eggs', 'spam'])
| |
− | | |
− | Inference from signature is simplest way to specify task arguments.
| |
− | Optional arguments (with default values), and special arguments like
| |
− | <code>self</code>, <code>*args</code> and <code>**kwargs</code> are
| |
− | ignored on iferrence:
| |
− | | |
− | >>> class MyTask(task.Task):
| |
− | ... def execute(self, spam, eggs=()):
| |
− | ... return spam + eggs
| |
− | ...
| |
− | >>> MyTask().requires
| |
− | set(['spam'])
| |
− | >>>
| |
− | >>> class UniTask(task.Task):
| |
− | ... def execute(self, *args, **kwargs):
| |
− | ... pass
| |
− | ...
| |
− | >>> UniTask().requires
| |
− | set([])
| |
− | | |
− | === Rebind ===
| |
− | There are cases when value you want to pass to task is stored with name other
| |
− | then corresponding task argument. That's when <code>rebind</code> task
| |
− | constructor parameter comes handy. Using it flow author can instruct engine
| |
− | to fetch a value from storage by one name, but pass it to task's
| |
− | <code>execute</code> method with another.
| |
− | | |
− | There are two possible way of using it. First is to pass dictionary that maps
| |
− | task argument name to name of saved value. For example, if you have task:
| |
− | | |
− | class SpawnVMTask(task.Task):
| |
− | def execute(self, vm_name, vm_image_id, **kwargs):
| |
− | pass # TODO(imelnikov): use paramters to spawn vm
| |
− | | |
− | and you saved vm name with 'name' key in storage, you can spawn vm with such
| |
− | name like this:
| |
− | | |
− | SpawnVMTask(rebind={'vm_name': 'name'})
| |
− | | |
− | Second, you can pass a tuple or list of argument names, and values with that
| |
− | names are passed to task. The length of tuple or list should not be less then
| |
− | number of task required parameters. For example, you can achieve same effect as
| |
− | the previous example with:
| |
− | | |
− | SpawnVMTask(rebind_args=('name', 'vm_image_id'))
| |
− | | |
− | which is equivalent to more elaborate
| |
− | | |
− | SpawnVMTask(rebind=dict(vm_name='name',
| |
− | vm_image_id='vm_image_id'))
| |
− | | |
− | In both cases, if your task accepts arbitrary arguments with
| |
− | <code>**kwargs</code> construct, you can specify extra arguments. For example:
| |
− | | |
− | SpawnVMTask(rebind=('name', 'vm_image_id', 'admin_key_name'))
| |
− | | |
− | When such task is about to be executed, <code>name</code>, <code>vm_image_id</code>
| |
− | and <code>admin_key_name</code> values are fetched from stroage, and, and
| |
− | value from <code>name</code> is passed to <code>execute</code> method as
| |
− | <code>vm_name</code>, value from <code>vm_image_id</code> is passed as
| |
− | <code>vm_image_id</code>, and value from <code>admin_key_name</code> is passed
| |
− | as <code>admin_key_name</code> parameter in <code>kwargs</code>.
| |
− | | |
− | === Manually Specifying Requirements ===
| |
− | | |
− | TODO(imelnikov): describe <code>requires</code> parameter, optional task
| |
− | args and <code>**kwargs</code>.
| |
− | | |
− | == Results Specification ==
| |
− | | |
− | In python, function results are not named, so we can not infer what task
| |
− | returns. Of course, complete task result (what <code>execute</code> method
| |
− | returns) is saved in storage, but it is not accessible by unless task
| |
− | specifies names or values via <code>provides</code> task constructor parameter.
| |
− | | |
− | === Returning One Value ===
| |
− | | |
− | If task returns just one value, <code>privodes</code> should be string -- the
| |
− | name of the value:
| |
− | | |
− | class TheAnswerReturningTask(task.Task):
| |
− | def execute(self):
| |
− | return 42
| |
− | | |
− | TheAnswerReturningTask(provides='the_answer')
| |
− | | |
− | === Returning Tuple ===
| |
− | | |
− | For task that returns several values, one option (as usual in python) is return
| |
− | a tuple:
| |
− | | |
− | class BitsAndPiecesTask(task.Task):
| |
− | def execute(self):
| |
− | return 'BITs', 'PIECEs'
| |
− | | |
− | Then, you can give the value individual names, by passing tuple or list as
| |
− | <code>provides</code> parameter:
| |
− |
| |
− | BitsAndPiecesTask(provides=('bits', 'pieces'))
| |
− | | |
− | After such task executes, you (and engine, which is useful for other tasks) will
| |
− | be able to get elements from storage by name:
| |
− | | |
− | >>> storage.fetch('bits')
| |
− | 'BITs'
| |
− | >>> storage.fetch('pieces')
| |
− | 'PIECEs'
| |
− | | |
− | Provides argument can be shorter then actual tuple returned by task -- then
| |
− | extra values are ignored (but, of course, saved and passed to
| |
− | <code>revert</code>).
| |
− | | |
− | Provides argument can be longer then actual tuple returned by task -- then extra
| |
− | parameters are left undefined: a warning is printed to logs and if use of such
| |
− | parameter is attempted <code>NotFound</code> exception is raised.
| |
− | | |
− | === Returning Dictionary ===
| |
− | | |
− | Other option to return several values is dictionary:
| |
− | | |
− | class BitsAndPiecesTask(task.Task):
| |
− | def execute(self):
| |
− | return {
| |
− | 'bits': 'BITs',
| |
− | 'pieces': 'PIECEs'
| |
− | }
| |
− | | |
− | TaskFlow expects that dict will be returened if <code>provides</code> argument
| |
− | is a <code>set</code>:
| |
− | | |
− | BitsAndPiecesTask(provides=set(['bits', 'pieces']))
| |
− | | |
− | After such task executes, you (and engine, which is useful for other tasks) will
| |
− | be able to get elements from storage by name:
| |
− | | |
− | >>> storage.fetch('bits')
| |
− | 'BITs'
| |
− | >>> storage.fetch('pieces')
| |
− | 'PIECEs'
| |
− | | |
− | Some items from dict returned by task can be not present in provides arguments
| |
− | -- then extra values are ignored (but, of course, saved and passed to
| |
− | <code>revert</code>).
| |
− | | |
− | Provides argument have some items not present in actual dict returned by task --
| |
− | then extra parameters are left undefined: a warning is printed to logs and if
| |
− | use of such parameter is attempted <code>NotFound</code> exception is raised.
| |
− | | |
− | === Default Provides ===
| |
− | | |
− | As mentioned above, by default task provides nothing, which means task results
| |
− | are not accessible by all the other tasks in the flow.
| |
− | | |
− | Task author can override this and specify default value for provides using
| |
− | <code>default_provides</code> class variable:
| |
− | | |
− | class BitsAndPiecesTask(task.Task):
| |
− | default_provides = ('bits', 'pieces')
| |
− | def execute(self):
| |
− | return 'BITs', 'PIECEs'
| |
− | | |
− | Of course, flow author can override this to change names:
| |
− | | |
− | BitsAndPiecesTask(provides=('b', 'p'))
| |
− | | |
− | or to change structure -- e.g. this instance will make whole tuple accessible to
| |
− | other tasks by name 'bnp':
| |
− | | |
− | BitsAndPiecesTask(provides='bnp')
| |
− | | |
− | or flow author may want to return default behavior and hide the results of the
| |
− | task from other tasks in the flow (e.g. to avoid naming conflicts):
| |
− | | |
− | BitsAndPiecesTask(provides=())
| |