dynflow 0.7.6 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +11 -3
- data/doc/pages/.gitignore +7 -0
- data/doc/pages/Gemfile +8 -0
- data/doc/pages/Rakefile +26 -0
- data/doc/pages/_config.yml +38 -0
- data/doc/pages/plugins/alert_block.rb +27 -0
- data/doc/pages/plugins/div_tag.rb +24 -0
- data/doc/pages/plugins/graphviz.rb +121 -0
- data/doc/pages/plugins/plantuml.rb +85 -0
- data/doc/pages/plugins/play.rb +13 -0
- data/doc/pages/plugins/tags.rb +138 -0
- data/doc/pages/plugins/toc.rb +20 -0
- data/doc/pages/source/.nojekyll +0 -0
- data/doc/pages/source/404.md +6 -0
- data/doc/pages/source/_includes/disqus.html +25 -0
- data/doc/pages/source/_includes/google_analytics.html +12 -0
- data/doc/pages/source/_includes/google_plus_one.html +2 -0
- data/doc/pages/source/_includes/menu.html +19 -0
- data/doc/pages/source/_includes/menu_brand.html +2 -0
- data/doc/pages/source/_includes/menu_right.html +1 -0
- data/doc/pages/source/_includes/post_item.html +10 -0
- data/doc/pages/source/_includes/scroll_to.html +24 -0
- data/doc/pages/source/_includes/twitter_sharing.html +9 -0
- data/doc/pages/source/_layouts/default.html +70 -0
- data/doc/pages/source/_layouts/page.html +47 -0
- data/doc/pages/source/_layouts/post.html +19 -0
- data/doc/pages/source/_layouts/presentation.html +39 -0
- data/doc/pages/source/_layouts/tag_page.html +12 -0
- data/doc/pages/source/_sass/_bootstrap-compass.scss +9 -0
- data/doc/pages/source/_sass/_bootstrap-mincer.scss +19 -0
- data/doc/pages/source/_sass/_bootstrap-sprockets.scss +9 -0
- data/doc/pages/source/_sass/_bootstrap-variables.sass +865 -0
- data/doc/pages/source/_sass/_bootstrap.scss +50 -0
- data/doc/pages/source/_sass/_specific.scss +16 -0
- data/doc/pages/source/_sass/_style.scss +172 -0
- data/doc/pages/source/_sass/bootstrap/_alerts.scss +73 -0
- data/doc/pages/source/_sass/bootstrap/_badges.scss +67 -0
- data/doc/pages/source/_sass/bootstrap/_breadcrumbs.scss +26 -0
- data/doc/pages/source/_sass/bootstrap/_button-groups.scss +243 -0
- data/doc/pages/source/_sass/bootstrap/_buttons.scss +160 -0
- data/doc/pages/source/_sass/bootstrap/_carousel.scss +269 -0
- data/doc/pages/source/_sass/bootstrap/_close.scss +36 -0
- data/doc/pages/source/_sass/bootstrap/_code.scss +69 -0
- data/doc/pages/source/_sass/bootstrap/_component-animations.scss +38 -0
- data/doc/pages/source/_sass/bootstrap/_dropdowns.scss +214 -0
- data/doc/pages/source/_sass/bootstrap/_forms.scss +570 -0
- data/doc/pages/source/_sass/bootstrap/_glyphicons.scss +301 -0
- data/doc/pages/source/_sass/bootstrap/_grid.scss +84 -0
- data/doc/pages/source/_sass/bootstrap/_input-groups.scss +166 -0
- data/doc/pages/source/_sass/bootstrap/_jumbotron.scss +50 -0
- data/doc/pages/source/_sass/bootstrap/_labels.scss +66 -0
- data/doc/pages/source/_sass/bootstrap/_list-group.scss +124 -0
- data/doc/pages/source/_sass/bootstrap/_media.scss +61 -0
- data/doc/pages/source/_sass/bootstrap/_mixins.scss +39 -0
- data/doc/pages/source/_sass/bootstrap/_modals.scss +148 -0
- data/doc/pages/source/_sass/bootstrap/_navbar.scss +663 -0
- data/doc/pages/source/_sass/bootstrap/_navs.scss +244 -0
- data/doc/pages/source/_sass/bootstrap/_normalize.scss +427 -0
- data/doc/pages/source/_sass/bootstrap/_pager.scss +54 -0
- data/doc/pages/source/_sass/bootstrap/_pagination.scss +88 -0
- data/doc/pages/source/_sass/bootstrap/_panels.scss +265 -0
- data/doc/pages/source/_sass/bootstrap/_popovers.scss +135 -0
- data/doc/pages/source/_sass/bootstrap/_print.scss +107 -0
- data/doc/pages/source/_sass/bootstrap/_progress-bars.scss +87 -0
- data/doc/pages/source/_sass/bootstrap/_responsive-embed.scss +35 -0
- data/doc/pages/source/_sass/bootstrap/_responsive-utilities.scss +177 -0
- data/doc/pages/source/_sass/bootstrap/_scaffolding.scss +150 -0
- data/doc/pages/source/_sass/bootstrap/_tables.scss +234 -0
- data/doc/pages/source/_sass/bootstrap/_theme.scss +273 -0
- data/doc/pages/source/_sass/bootstrap/_thumbnails.scss +38 -0
- data/doc/pages/source/_sass/bootstrap/_tooltip.scss +103 -0
- data/doc/pages/source/_sass/bootstrap/_type.scss +298 -0
- data/doc/pages/source/_sass/bootstrap/_utilities.scss +56 -0
- data/doc/pages/source/_sass/bootstrap/_variables.scss +862 -0
- data/doc/pages/source/_sass/bootstrap/_wells.scss +29 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_alerts.scss +14 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_background-variant.scss +11 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_border-radius.scss +18 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_buttons.scss +52 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_center-block.scss +7 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_clearfix.scss +22 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_forms.scss +88 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_gradients.scss +58 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_grid-framework.scss +81 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_grid.scss +122 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_hide-text.scss +21 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_image.scss +33 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_labels.scss +12 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_list-group.scss +31 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_nav-divider.scss +10 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_nav-vertical-align.scss +9 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_opacity.scss +8 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_pagination.scss +23 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_panels.scss +24 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_progress-bar.scss +10 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_reset-filter.scss +8 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_resize.scss +6 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_responsive-visibility.scss +21 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_size.scss +10 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_tab-focus.scss +9 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_table-row.scss +28 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_text-emphasis.scss +11 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_text-overflow.scss +8 -0
- data/doc/pages/source/_sass/bootstrap/mixins/_vendor-prefixes.scss +222 -0
- data/doc/pages/source/atom.xml +32 -0
- data/doc/pages/source/bootstrap/config.json +429 -0
- data/doc/pages/source/bootstrap/css/bootstrap-theme.css +479 -0
- data/doc/pages/source/bootstrap/css/bootstrap-theme.min.css +10 -0
- data/doc/pages/source/bootstrap/css/bootstrap.css +6564 -0
- data/doc/pages/source/bootstrap/css/bootstrap.min.css +10 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- data/doc/pages/source/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/doc/pages/source/bootstrap/js/bootstrap.js +2309 -0
- data/doc/pages/source/bootstrap/js/bootstrap.min.js +12 -0
- data/doc/pages/source/css/app.scss +10 -0
- data/doc/pages/source/css/syntax.css +60 -0
- data/doc/pages/source/documentation/index.md +977 -0
- data/doc/pages/source/faq/index.md +16 -0
- data/doc/pages/source/images/dynflow-logos.svg +423 -0
- data/doc/pages/source/images/logo-long.png +0 -0
- data/doc/pages/source/images/logo-long.svg +116 -0
- data/doc/pages/source/images/logo-square.png +0 -0
- data/doc/pages/source/images/logo-square.svg +75 -0
- data/doc/pages/source/images/noise.png +0 -0
- data/doc/pages/source/images/screenshot.png +0 -0
- data/doc/pages/source/index.md +64 -0
- data/doc/pages/source/media/index.md +20 -0
- data/doc/pages/source/projects/index.md +32 -0
- data/dynflow.gemspec +2 -3
- data/examples/sub_plans.rb +37 -0
- data/lib/dynflow/action.rb +28 -8
- data/lib/dynflow/action/polling.rb +6 -5
- data/lib/dynflow/action/with_sub_plans.rb +162 -0
- data/lib/dynflow/execution_plan.rb +25 -7
- data/lib/dynflow/execution_plan/steps/abstract.rb +5 -2
- data/lib/dynflow/execution_plan/steps/plan_step.rb +6 -2
- data/lib/dynflow/execution_plan/steps/run_step.rb +4 -0
- data/lib/dynflow/persistence.rb +5 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +12 -2
- data/lib/dynflow/persistence_adapters/sequel_migrations/003_parent_action.rb +9 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +21 -7
- data/lib/dynflow/world.rb +26 -2
- data/test/action_test.rb +107 -0
- data/test/persistance_adapters_test.rb +2 -2
- data/test/test_helper.rb +1 -1
- data/web/views/flow_step.erb +3 -0
- metadata +137 -4
|
Binary file
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
6
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
|
7
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
8
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
width="156.38499"
|
|
13
|
+
height="156.38499"
|
|
14
|
+
viewBox="0 0 156.385 156.38499"
|
|
15
|
+
id="svg3634"
|
|
16
|
+
version="1.1"
|
|
17
|
+
inkscape:version="0.91 r13725"
|
|
18
|
+
sodipodi:docname="logo-square.svg"
|
|
19
|
+
inkscape:export-filename="/Users/pitr/Workspace/redhat/dynflow/pages/source/images/logo-square.png"
|
|
20
|
+
inkscape:export-xdpi="73.660004"
|
|
21
|
+
inkscape:export-ydpi="73.660004">
|
|
22
|
+
<defs
|
|
23
|
+
id="defs3636" />
|
|
24
|
+
<sodipodi:namedview
|
|
25
|
+
id="base"
|
|
26
|
+
pagecolor="#ffffff"
|
|
27
|
+
bordercolor="#666666"
|
|
28
|
+
borderopacity="1.0"
|
|
29
|
+
inkscape:pageopacity="0.0"
|
|
30
|
+
inkscape:pageshadow="2"
|
|
31
|
+
inkscape:zoom="0.98994949"
|
|
32
|
+
inkscape:cx="-118.07845"
|
|
33
|
+
inkscape:cy="174.77927"
|
|
34
|
+
inkscape:document-units="px"
|
|
35
|
+
inkscape:current-layer="layer1"
|
|
36
|
+
showgrid="false"
|
|
37
|
+
units="px"
|
|
38
|
+
inkscape:window-width="1920"
|
|
39
|
+
inkscape:window-height="977"
|
|
40
|
+
inkscape:window-x="0"
|
|
41
|
+
inkscape:window-y="0"
|
|
42
|
+
inkscape:window-maximized="1" />
|
|
43
|
+
<metadata
|
|
44
|
+
id="metadata3639">
|
|
45
|
+
<rdf:RDF>
|
|
46
|
+
<cc:Work
|
|
47
|
+
rdf:about="">
|
|
48
|
+
<dc:format>image/svg+xml</dc:format>
|
|
49
|
+
<dc:type
|
|
50
|
+
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
51
|
+
<dc:title />
|
|
52
|
+
</cc:Work>
|
|
53
|
+
</rdf:RDF>
|
|
54
|
+
</metadata>
|
|
55
|
+
<g
|
|
56
|
+
inkscape:label="Vrstva 1"
|
|
57
|
+
inkscape:groupmode="layer"
|
|
58
|
+
id="layer1"
|
|
59
|
+
transform="translate(0,-895.97718)">
|
|
60
|
+
<g
|
|
61
|
+
id="g3650"
|
|
62
|
+
transform="matrix(0.96003386,0,0,0.96003386,-198.84571,240.90468)">
|
|
63
|
+
<path
|
|
64
|
+
inkscape:connector-curvature="0"
|
|
65
|
+
id="path4180"
|
|
66
|
+
d="m 273.38806,813.90518 c 10.67131,0 21.34262,0 32.01393,0 2.80651,-14.44298 5.61302,-28.88596 8.41953,-43.32896 14.0093,0 28.0186,0 42.0279,0 1.32345,-6.63958 2.64689,-13.27917 3.97034,-19.91875 -14.03211,0 -28.06422,0 -42.09632,0 1.2318,-6.34315 2.46359,-12.6863 3.69539,-19.02944 14.89948,0 29.79898,0 44.69847,0 1.30064,-6.63959 2.60127,-13.27917 3.90192,-19.91876 -21.72018,0.008 -43.44037,0.0166 -65.16055,0.025 -13.50291,13.09022 -17.93615,33.58442 -13.92119,51.63274 4.1749,18.53456 -3.50427,38.1567 -17.54942,50.53818 z"
|
|
67
|
+
style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:199.62593079px;line-height:521.00000381%;font-family:Sans;-inkscape-font-specification:'Sans Bold Italic';letter-spacing:-15.00960922;word-spacing:0px;fill:#f78a29;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
|
68
|
+
<path
|
|
69
|
+
inkscape:connector-curvature="0"
|
|
70
|
+
id="path4182"
|
|
71
|
+
d="m 302.88929,711.73558 c -8.97951,0.003 -17.95902,0.007 -26.93853,0.0105 -2.53902,13.13095 -5.07803,26.26191 -7.61704,39.39286 -3.94471,-7.6785 -12.21396,-12.70145 -20.86255,-12.52779 -13.12339,-0.65457 -24.86575,7.77854 -31.69186,18.45936 -7.81528,12.04757 -10.96616,27.6448 -6.82946,41.55316 2.90262,9.5693 12.03429,16.63718 22.02555,17.062 9.83511,1.18439 19.6167,-3.36795 25.89418,-10.86051 2.40839,-3.54988 0.50699,1.60166 0.39438,3.15516 -0.40232,1.97496 -0.80464,3.9499 -1.20696,5.92485 4.97717,0 9.95433,0 14.9315,0 6.23473,-4.38616 10.69735,-10.95265 14.26689,-17.58865 5.72598,-10.85952 6.75245,-23.72144 3.82667,-35.55166 -3.03798,-17.2946 1.49379,-36.20142 13.80723,-49.02929 z m -50.96838,43.93282 c 5.88513,-0.46419 11.40435,4.14517 11.87951,10.04989 0.83507,10.60817 -1.53732,22.75724 -10.22572,29.76296 -5.55537,4.22384 -15.26841,4.98105 -19.7752,-1.15819 -3.69118,-6.08712 -2.40972,-13.72962 -1.09341,-20.34581 2.18961,-8.98104 8.97423,-18.5654 19.21482,-18.30885 z"
|
|
72
|
+
style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:199.62593079px;line-height:521.00000381%;font-family:Sans;-inkscape-font-specification:'Sans Bold Italic';letter-spacing:-15.00960922;word-spacing:0px;fill:#289fd5;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
|
73
|
+
</g>
|
|
74
|
+
</g>
|
|
75
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: page
|
|
3
|
+
title: About
|
|
4
|
+
skip-title: true
|
|
5
|
+
sharing: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<div id="big_logo" class="well"><img src="/images/logo-long.png"></div>
|
|
9
|
+
|
|
10
|
+
Dynflow (**DYN**amic work**FLOW**) is a workflow engine
|
|
11
|
+
written in Ruby that allows to:
|
|
12
|
+
|
|
13
|
+
- Keep track of the progress of running processes
|
|
14
|
+
- Run the code asynchronously
|
|
15
|
+
- When something goes wrong, pause the process, optionally let user interact,
|
|
16
|
+
resume the process, skip some steps when needed
|
|
17
|
+
- Detect independent parts and run them concurrently
|
|
18
|
+
- Compose simple actions into more complex scenarios
|
|
19
|
+
- Extend the workflows from third-party libraries
|
|
20
|
+
- Keep consistency between local transactional database and
|
|
21
|
+
external services
|
|
22
|
+
- Suspend the long-running steps, not blocking the thread pool
|
|
23
|
+
- Cancel steps when possible
|
|
24
|
+
- Extend the actions behavior with middlewares
|
|
25
|
+
- Pick different adapters to provide: storage backend, transactions, or executor implementation
|
|
26
|
+
|
|
27
|
+
Dynflow has been developed to be able to support orchestration of services in the
|
|
28
|
+
[Katello](http://katello.org) and [Foreman](http://theforeman.org/) projects.
|
|
29
|
+
|
|
30
|
+
### Planned features
|
|
31
|
+
|
|
32
|
+
- Define the input/output interface between the building blocks
|
|
33
|
+
- Define rollback for the workflow
|
|
34
|
+
- Have multiple workers for distributing the load (in progress)
|
|
35
|
+
- Migration to [concurrent-ruby](http://concurrent-ruby.com) (in progress)
|
|
36
|
+
|
|
37
|
+
## Getting started
|
|
38
|
+
|
|
39
|
+
### Requirements
|
|
40
|
+
|
|
41
|
+
- Ruby MRI 1.9.3, 2.0, or 2.1.
|
|
42
|
+
- JRuby and Rubinius support is on the way.
|
|
43
|
+
|
|
44
|
+
### Installation
|
|
45
|
+
|
|
46
|
+
`gem install dynflow`
|
|
47
|
+
|
|
48
|
+
*TODO*
|
|
49
|
+
|
|
50
|
+
## Links
|
|
51
|
+
|
|
52
|
+
- [Github](https://github.com/dynflow/dynflow)
|
|
53
|
+
|
|
54
|
+
## Current status
|
|
55
|
+
|
|
56
|
+

|
|
57
|
+

|
|
58
|
+

|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
## Authors
|
|
62
|
+
|
|
63
|
+
- [Ivan Nečas](https://github.com/iNecas)
|
|
64
|
+
- [Petr Chalupa](http://blog.pitr.ch)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: page
|
|
3
|
+
title: "Media"
|
|
4
|
+
comments: false
|
|
5
|
+
sharing: false
|
|
6
|
+
footer: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Presentations
|
|
10
|
+
|
|
11
|
+
*TODO add*
|
|
12
|
+
|
|
13
|
+
## Deep-dives
|
|
14
|
+
|
|
15
|
+
- [Foreman deep dive: Rewriting orchestration to Dynflow](https://www.youtube.com/watch?v=fXKgEvoAQpg)
|
|
16
|
+
- [Foreman deep dive: Rewriting orchestration to Dynflow (part 2)](https://www.youtube.com/watch?v=hOouaJRk3U4)
|
|
17
|
+
- [MCOflow - MCollective over Dynflow](https://www.youtube.com/watch?v=UyO4R7VUFZ4)
|
|
18
|
+
- [Katello DynFlow Walkthrough](https://www.youtube.com/watch?v=CEIdXFA5CU0)
|
|
19
|
+
- [Foreman deep dive: Dynflow tools and concepts (part 1 of 2)](https://www.youtube.com/watch?v=5dprXA7gbSI)
|
|
20
|
+
- [Foreman deep dive: Dynflow executors (part 2 of 2)](https://www.youtube.com/watch?v=drBY8Z1CcKs)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: page
|
|
3
|
+
title: "Related projects"
|
|
4
|
+
comments: false
|
|
5
|
+
sharing: false
|
|
6
|
+
footer: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Used by
|
|
10
|
+
|
|
11
|
+
- [Foreman](http://theforeman.org) - lifecycle management tool for
|
|
12
|
+
physical and virtual servers.
|
|
13
|
+
- [Katello](http://katello.org) - content management plugin for
|
|
14
|
+
Foreman: integrates couple of REST services for managing the
|
|
15
|
+
software updates in the infrastructure.
|
|
16
|
+
- [Foreman-tasks](https://github.com/iNecas/foreman-tasks) - Foreman
|
|
17
|
+
plugin providing the tasks management with Dynflow on the back-end.
|
|
18
|
+
- [Dyntask](https://github.com/iNecas/dyntask) - generic Rails engine
|
|
19
|
+
providing the tasks management features with Dynflow on the back-end.
|
|
20
|
+
- [Sysflow](https://github.com/iNecas/sysflow) - set of reusable tools
|
|
21
|
+
for running system tasks with Dynflow, comes with simple Web-UI for
|
|
22
|
+
testing it.
|
|
23
|
+
|
|
24
|
+
## Using
|
|
25
|
+
|
|
26
|
+
- [Algebrick](https://github.com/pitr-ch/algebrick) - Typed structs on steroids based on
|
|
27
|
+
algebraic types and pattern matching.
|
|
28
|
+
- [concurrent-ruby](http://www.concurrent-ruby.com/) - Modern concurrency tools including agents,
|
|
29
|
+
futures, promises, thread pools, supervisors, and more. Inspired by Erlang, Clojure, Scala,
|
|
30
|
+
Go, Java, JavaScript, and classic concurrency patterns.
|
|
31
|
+
|
|
32
|
+
|
data/dynflow.gemspec
CHANGED
|
@@ -5,12 +5,11 @@ require "dynflow/version"
|
|
|
5
5
|
Gem::Specification.new do |s|
|
|
6
6
|
s.name = "dynflow"
|
|
7
7
|
s.version = Dynflow::VERSION
|
|
8
|
-
s.authors = ["Ivan Necas"]
|
|
8
|
+
s.authors = ["Ivan Necas", "Petr Chalupa"]
|
|
9
9
|
s.email = ["inecas@redhat.com"]
|
|
10
10
|
s.homepage = "http://github.com/Dynflow/dynflow"
|
|
11
11
|
s.summary = "DYNamic workFLOW engine"
|
|
12
|
-
s.description = "
|
|
13
|
-
"on input data and leave it open for others to jump into it as well"
|
|
12
|
+
s.description = "Ruby workflow/orchestration engine"
|
|
14
13
|
s.license = "MIT"
|
|
15
14
|
|
|
16
15
|
s.files = `git ls-files`.split("\n")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
example_description = <<DESC
|
|
4
|
+
Sub Plans Example
|
|
5
|
+
===================
|
|
6
|
+
|
|
7
|
+
This example shows, how to trigger the execution plans from within a
|
|
8
|
+
run method of some action and waing for them to finish.
|
|
9
|
+
|
|
10
|
+
This is useful, when doing bulk actions, where having one big
|
|
11
|
+
execution plan would not be effective, or in case all the data are
|
|
12
|
+
not available by the time of original action planning.
|
|
13
|
+
|
|
14
|
+
DESC
|
|
15
|
+
|
|
16
|
+
require_relative 'example_helper'
|
|
17
|
+
require_relative 'orchestrate_evented'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SubPlansExample < Dynflow::Action
|
|
21
|
+
include Dynflow::Action::WithSubPlans
|
|
22
|
+
|
|
23
|
+
def create_sub_plans
|
|
24
|
+
10.times.map { |i| trigger(OrchestrateEvented::CreateMachine, "host-#{i}", 'web_server') }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if $0 == __FILE__
|
|
29
|
+
triggered = ExampleHelper.world.trigger(SubPlansExample)
|
|
30
|
+
puts example_description
|
|
31
|
+
puts <<-MSG.gsub(/^.*\|/, '')
|
|
32
|
+
| Execution plan #{triggered.id} with sub plans triggered
|
|
33
|
+
| You can see the details at http://localhost:4567/#{triggered.id}
|
|
34
|
+
MSG
|
|
35
|
+
|
|
36
|
+
ExampleHelper.run_web_console
|
|
37
|
+
end
|
data/lib/dynflow/action.rb
CHANGED
|
@@ -22,6 +22,7 @@ module Dynflow
|
|
|
22
22
|
|
|
23
23
|
require 'dynflow/action/polling'
|
|
24
24
|
require 'dynflow/action/cancellable'
|
|
25
|
+
require 'dynflow/action/with_sub_plans'
|
|
25
26
|
|
|
26
27
|
def self.all_children
|
|
27
28
|
children.values.inject(children.values) do |children, child|
|
|
@@ -83,7 +84,8 @@ module Dynflow
|
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
attr_reader :world, :phase, :execution_plan_id, :id, :input,
|
|
86
|
-
:plan_step_id, :run_step_id, :finalize_step_id
|
|
87
|
+
:plan_step_id, :run_step_id, :finalize_step_id,
|
|
88
|
+
:caller_execution_plan_id, :caller_action_id
|
|
87
89
|
|
|
88
90
|
middleware.use Action::Progress::Calculate
|
|
89
91
|
|
|
@@ -102,6 +104,9 @@ module Dynflow
|
|
|
102
104
|
|
|
103
105
|
@execution_plan = Type!(attributes.fetch(:execution_plan), ExecutionPlan) if phase? Present
|
|
104
106
|
|
|
107
|
+
@caller_execution_plan_id = Type!(attributes.fetch(:caller_execution_plan_id, nil), String, NilClass)
|
|
108
|
+
@caller_action_id = Type!(attributes.fetch(:caller_action_id, nil), Integer, NilClass)
|
|
109
|
+
|
|
105
110
|
getter =-> key, required do
|
|
106
111
|
required ? attributes.fetch(key) : attributes.fetch(key, {})
|
|
107
112
|
end
|
|
@@ -140,6 +145,19 @@ module Dynflow
|
|
|
140
145
|
end
|
|
141
146
|
end
|
|
142
147
|
|
|
148
|
+
def caller_action
|
|
149
|
+
plase! Present
|
|
150
|
+
return nil if @caller_action_id
|
|
151
|
+
return @caller_action if @caller_action
|
|
152
|
+
|
|
153
|
+
caller_execution_plan = if @caller_execution_plan_id == execution_plan.id
|
|
154
|
+
execution_plan
|
|
155
|
+
else
|
|
156
|
+
world.persistence.load_execution_plan(@caller_execution_plan_id)
|
|
157
|
+
end
|
|
158
|
+
@caller_action = world.persistence.load_action_for_presentation(caller_execution_plan, @caller_action_id)
|
|
159
|
+
end
|
|
160
|
+
|
|
143
161
|
def set_plan_context(execution_plan, trigger, from_subscription)
|
|
144
162
|
phase! Plan
|
|
145
163
|
@execution_plan = Type! execution_plan, ExecutionPlan
|
|
@@ -209,13 +227,15 @@ module Dynflow
|
|
|
209
227
|
|
|
210
228
|
def to_hash
|
|
211
229
|
recursive_to_hash(
|
|
212
|
-
{ class:
|
|
213
|
-
execution_plan_id:
|
|
214
|
-
id:
|
|
215
|
-
plan_step_id:
|
|
216
|
-
run_step_id:
|
|
217
|
-
finalize_step_id:
|
|
218
|
-
|
|
230
|
+
{ class: self.class.name,
|
|
231
|
+
execution_plan_id: execution_plan_id,
|
|
232
|
+
id: id,
|
|
233
|
+
plan_step_id: plan_step_id,
|
|
234
|
+
run_step_id: run_step_id,
|
|
235
|
+
finalize_step_id: finalize_step_id,
|
|
236
|
+
caller_execution_plan_id: caller_execution_plan_id,
|
|
237
|
+
caller_action_id: caller_action_id,
|
|
238
|
+
input: input },
|
|
219
239
|
if phase? Run, Finalize, Present
|
|
220
240
|
{ output: output }
|
|
221
241
|
end)
|
|
@@ -16,6 +16,7 @@ module Dynflow
|
|
|
16
16
|
else
|
|
17
17
|
raise "unrecognized event #{event}"
|
|
18
18
|
end
|
|
19
|
+
done? ? on_finish : suspend_and_ping
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def done?
|
|
@@ -30,6 +31,9 @@ module Dynflow
|
|
|
30
31
|
raise NotImplementedError
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
def on_finish
|
|
35
|
+
end
|
|
36
|
+
|
|
33
37
|
# External task data. It should return nil when the task has not
|
|
34
38
|
# been triggered yet.
|
|
35
39
|
def external_task
|
|
@@ -68,7 +72,6 @@ module Dynflow
|
|
|
68
72
|
|
|
69
73
|
def initiate_external_action
|
|
70
74
|
self.external_task = invoke_external_task
|
|
71
|
-
suspend_and_ping unless done?
|
|
72
75
|
end
|
|
73
76
|
|
|
74
77
|
def resume_external_action
|
|
@@ -85,21 +88,19 @@ module Dynflow
|
|
|
85
88
|
poll_attempts[:total] += 1
|
|
86
89
|
self.external_task = poll_external_task
|
|
87
90
|
poll_attempts[:failed] = 0
|
|
88
|
-
suspend_and_ping unless done?
|
|
89
91
|
rescue => error
|
|
90
92
|
poll_attempts[:failed] += 1
|
|
91
|
-
|
|
93
|
+
rescue_external_task(error)
|
|
92
94
|
end
|
|
93
95
|
|
|
94
96
|
def poll_attempts
|
|
95
97
|
output[:poll_attempts] ||= { total: 0, failed: 0 }
|
|
96
98
|
end
|
|
97
99
|
|
|
98
|
-
def
|
|
100
|
+
def rescue_external_task(error)
|
|
99
101
|
if poll_attempts[:failed] < poll_max_retries
|
|
100
102
|
action_logger.warn("Polling failed, attempt no. #{poll_attempts[:failed]}, retrying in #{poll_interval}")
|
|
101
103
|
action_logger.warn(error)
|
|
102
|
-
suspend_and_ping
|
|
103
104
|
else
|
|
104
105
|
raise error
|
|
105
106
|
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Action::WithSubPlans
|
|
3
|
+
SubPlanFinished = Algebrick.type do
|
|
4
|
+
fields! :execution_plan_id => String,
|
|
5
|
+
:success => type { variants TrueClass, FalseClass }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def run(event = nil)
|
|
9
|
+
match event,
|
|
10
|
+
(on nil do
|
|
11
|
+
if output[:total_count]
|
|
12
|
+
resume
|
|
13
|
+
else
|
|
14
|
+
initiate
|
|
15
|
+
end
|
|
16
|
+
end),
|
|
17
|
+
(on SubPlanFinished do
|
|
18
|
+
mark_as_done(event.execution_plan_id, event.success)
|
|
19
|
+
try_to_finish or suspend
|
|
20
|
+
end)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initiate
|
|
24
|
+
sub_plans = create_sub_plans
|
|
25
|
+
sub_plans = Array[sub_plans] unless sub_plans.is_a? Array
|
|
26
|
+
wait_for_sub_plans(sub_plans)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @abstract when the logic for the initiation of the subtasks
|
|
30
|
+
# is different from the default one.
|
|
31
|
+
# @returns a triggered task or array of triggered tasks
|
|
32
|
+
# @example
|
|
33
|
+
#
|
|
34
|
+
# def create_sub_plans
|
|
35
|
+
# trigger(MyAction, "Hello")
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# @example
|
|
39
|
+
#
|
|
40
|
+
# def create_sub_plans
|
|
41
|
+
# [trigger(MyAction, "Hello 1"), trigger(MyAction, "Hello 2")]
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
def create_sub_plans
|
|
45
|
+
raise NotImplementedError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @api method to be called after all the sub tasks finished
|
|
49
|
+
def on_finish
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Helper for creating sub plans
|
|
53
|
+
def trigger(*args)
|
|
54
|
+
world.trigger do
|
|
55
|
+
world.plan_with_caller(self, *args)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def wait_for_sub_plans(sub_plans)
|
|
60
|
+
output.update(total_count: 0,
|
|
61
|
+
failed_count: 0,
|
|
62
|
+
success_count: 0)
|
|
63
|
+
|
|
64
|
+
planned, failed = sub_plans.partition(&:planned?)
|
|
65
|
+
|
|
66
|
+
sub_plan_ids = (planned + failed).map(&:execution_plan_id)
|
|
67
|
+
|
|
68
|
+
output[:total_count] = sub_plan_ids.size
|
|
69
|
+
output[:failed_count] = failed.size
|
|
70
|
+
|
|
71
|
+
if planned.any?
|
|
72
|
+
notify_on_finish(planned)
|
|
73
|
+
else
|
|
74
|
+
check_for_errors!
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def try_to_finish
|
|
79
|
+
if done?
|
|
80
|
+
check_for_errors!
|
|
81
|
+
on_finish
|
|
82
|
+
return true
|
|
83
|
+
else
|
|
84
|
+
return false
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def resume
|
|
89
|
+
if sub_plans.all? { |sub_plan| sub_plan.error_in_plan? }
|
|
90
|
+
initiate
|
|
91
|
+
else
|
|
92
|
+
recalculate_counts
|
|
93
|
+
try_to_finish or fail "Some sub plans are still not finished"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def sub_plans
|
|
98
|
+
@sub_plans ||= world.persistence.find_execution_plans(filters: { 'caller_execution_plan_id' => execution_plan_id,
|
|
99
|
+
'caller_action_id' => self.id } )
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def notify_on_finish(plans)
|
|
103
|
+
suspend do |suspended_action|
|
|
104
|
+
plans.each do |plan|
|
|
105
|
+
plan.finished.do_then do |value|
|
|
106
|
+
suspended_action << SubPlanFinished[plan.execution_plan_id,
|
|
107
|
+
value.result == :success]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def mark_as_done(plan_id, success)
|
|
114
|
+
if success
|
|
115
|
+
output[:success_count] += 1
|
|
116
|
+
else
|
|
117
|
+
output[:failed_count] += 1
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def done?
|
|
122
|
+
if counts_set?
|
|
123
|
+
output[:total_count] - output[:success_count] - output[:failed_count] <= 0
|
|
124
|
+
else
|
|
125
|
+
false
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def run_progress
|
|
130
|
+
if counts_set? && output[:total_count] > 0
|
|
131
|
+
(output[:success_count] + output[:failed_count]).to_f / output[:total_count]
|
|
132
|
+
else
|
|
133
|
+
0.1
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def recalculate_counts
|
|
138
|
+
output.update(total_count: 0,
|
|
139
|
+
failed_count: 0,
|
|
140
|
+
success_count: 0)
|
|
141
|
+
sub_plans.each do |sub_plan|
|
|
142
|
+
output[:total_count] += 1
|
|
143
|
+
if sub_plan.state == :stopped
|
|
144
|
+
if sub_plan.error?
|
|
145
|
+
output[:failed_count] += 1
|
|
146
|
+
else
|
|
147
|
+
output[:success_count] += 1
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def counts_set?
|
|
154
|
+
output[:total_count] && output[:success_count] && output[:failed_count]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def check_for_errors!
|
|
158
|
+
fail "A sub task failed" if output[:failed_count] > 0
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
end
|
|
162
|
+
end
|