dwf 0.1.9 → 0.1.13
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.
- checksums.yaml +4 -4
- data/.github/workflows/build_gem.yaml +0 -4
- data/.github/workflows/test.yaml +5 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +127 -0
- data/README.md +150 -36
- data/dwf.gemspec +9 -6
- data/lib/dwf/callback.rb +16 -14
- data/lib/dwf/client.rb +82 -9
- data/lib/dwf/concerns/checkable.rb +29 -0
- data/lib/dwf/errors.rb +3 -0
- data/lib/dwf/item.rb +55 -39
- data/lib/dwf/utils.rb +6 -0
- data/lib/dwf/version.rb +1 -1
- data/lib/dwf/workflow.rb +154 -35
- data/spec/dwf/client_spec.rb +109 -18
- data/spec/dwf/item_spec.rb +86 -33
- data/spec/dwf/utils_spec.rb +9 -0
- data/spec/dwf/workflow_spec.rb +376 -0
- data/spec/spec_helper.rb +3 -0
- metadata +42 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 131525481d493d4765a33fa9b5d34524f74f221b05399b1a39b2cc47d439aa65
|
4
|
+
data.tar.gz: 4483fe6f6e98e45579d65362c2ba3f0298303522d103647c0a3d04085580af16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a5e357a59d77faa86eaa54552f19b2ad06b82af8a72bc80a2c66fd8ba56584391c08878c0e0b16cf32df6e6dc564d01bf60b0c16b2bcafe7649b6522643db48
|
7
|
+
data.tar.gz: 056e815c2beed08ca7e8fccc0c22afb291c849f9195b1bd5d7469d22096b62a37a8a457a21eeaa949f436e034586217e3aacf4e7d6cf44e52f325c88126c8b6c
|
data/.github/workflows/test.yaml
CHANGED
@@ -13,12 +13,16 @@ jobs:
|
|
13
13
|
|
14
14
|
runs-on: ubuntu-latest
|
15
15
|
|
16
|
+
strategy:
|
17
|
+
matrix:
|
18
|
+
ruby-version: ["2.5", "2.6", "2.7", "3.0"]
|
19
|
+
|
16
20
|
steps:
|
17
21
|
- uses: actions/checkout@v2
|
18
22
|
- name: Set up Ruby
|
19
23
|
uses: ruby/setup-ruby@477b21f02be01bcb8030d50f37cfec92bfa615b6
|
20
24
|
with:
|
21
|
-
ruby-version:
|
25
|
+
ruby-version: ${{ matrix.ruby-version }}
|
22
26
|
- name: Install dependencies
|
23
27
|
run: bundle install
|
24
28
|
- name: Run tests
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,132 @@
|
|
1
1
|
# Changelog
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
## 0.1.12
|
5
|
+
### Added
|
6
|
+
#### Dynamic workflows
|
7
|
+
There might be a case when you have to contruct the workflow dynamically depend on the input
|
8
|
+
As an example, let's write a workflow which puts from 1 to 100 into the terminal parallely . Additionally after finish all job, it will puts the finshed word into the terminal
|
9
|
+
```ruby
|
10
|
+
class FirstMainItem < Dwf::Item
|
11
|
+
def perform
|
12
|
+
puts "#{self.class.name}: running #{params}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
SecondMainItem = Class.new(FirstMainItem)
|
17
|
+
|
18
|
+
class TestWf < Dwf::Workflow
|
19
|
+
def configure
|
20
|
+
items = (1..100).to_a.map do |number|
|
21
|
+
run FirstMainItem, params: number
|
22
|
+
end
|
23
|
+
run SecondMainItem, after: items, params: "finished"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
```
|
28
|
+
We can achieve that because run method returns the id of the created job, which we can use for chaining dependencies.
|
29
|
+
Now, when we create the workflow like this:
|
30
|
+
```ruby
|
31
|
+
wf = TestWf.create
|
32
|
+
# wf.callback_type = Dwf::Workflow::SK_BATCH
|
33
|
+
wf.start!
|
34
|
+
```
|
35
|
+
|
36
|
+
## 0.1.12
|
37
|
+
### Added
|
38
|
+
#### Subworkflow for all callback types
|
39
|
+
same with `0.1.11`
|
40
|
+
## 0.1.11
|
41
|
+
### Added
|
42
|
+
#### Subworkflow - Only support sidekiq pro
|
43
|
+
There might be a case when you want to reuse a workflow in another workflow
|
44
|
+
|
45
|
+
As an example, let's write a workflow which contain another workflow, expected that the SubWorkflow workflow execute after `SecondItem` and the `ThirdItem` execute after `SubWorkflow`
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
gem 'dwf', '~> 0.1.11'
|
49
|
+
```
|
50
|
+
|
51
|
+
### Setup
|
52
|
+
```ruby
|
53
|
+
class FirstItem < Dwf::Item
|
54
|
+
def perform
|
55
|
+
puts "Main flow: #{self.class.name} running"
|
56
|
+
puts "Main flow: #{self.class.name} finish"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
SecondItem = Class.new(FirstItem)
|
61
|
+
ThirtItem = Class.new(FirstItem)
|
62
|
+
|
63
|
+
class FirstSubItem < Dwf::Item
|
64
|
+
def perform
|
65
|
+
puts "Sub flow: #{self.class.name} running"
|
66
|
+
puts "Sub flow: #{self.class.name} finish"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
SecondSubItem = Class.new(FirstSubItem)
|
71
|
+
|
72
|
+
class SubWorkflow < Dwf::Workflow
|
73
|
+
def configure
|
74
|
+
run FirstSubItem
|
75
|
+
run SecondSubItem, after: FirstSubItem
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
class TestWf < Dwf::Workflow
|
81
|
+
def configure
|
82
|
+
run FirstItem
|
83
|
+
run SecondItem, after: FirstItem
|
84
|
+
run SubWorkflow, after: SecondItem
|
85
|
+
run ThirtItem, after: SubWorkflow
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
wf = TestWf.create
|
90
|
+
wf.start!
|
91
|
+
```
|
92
|
+
|
93
|
+
### Result
|
94
|
+
```
|
95
|
+
Main flow: FirstItem running
|
96
|
+
Main flow: FirstItem finish
|
97
|
+
Main flow: SecondItem running
|
98
|
+
Main flow: SecondItem finish
|
99
|
+
Sub flow: FirstSubItem running
|
100
|
+
Sub flow: FirstSubItem finish
|
101
|
+
Sub flow: SecondSubItem running
|
102
|
+
Sub flow: SecondSubItem finish
|
103
|
+
Main flow: ThirtItem running
|
104
|
+
Main flow: ThirtItem finish
|
105
|
+
```
|
106
|
+
|
107
|
+
## 0.1.10
|
108
|
+
### Added
|
109
|
+
- Allow to use argument within workflow and update the defining callback way
|
110
|
+
```
|
111
|
+
class TestWf < Dwf::Workflow
|
112
|
+
def configure(arguments)
|
113
|
+
run A
|
114
|
+
run B, after: A, params: argument
|
115
|
+
run C, after: A, params: argument
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
wf = TestWf.create(arguments)
|
120
|
+
wf.callback_type = Dwf::Workflow::SK_BATCH
|
121
|
+
|
122
|
+
```
|
123
|
+
- Support `find` workflow and `reload` workflow
|
124
|
+
```
|
125
|
+
wf = TestWf.create
|
126
|
+
Dwf::Workflow.find(wf.id)
|
127
|
+
wf.reload
|
128
|
+
```
|
129
|
+
|
3
130
|
## 0.1.9
|
4
131
|
### Added
|
5
132
|
### Fixed
|
data/README.md
CHANGED
@@ -1,25 +1,48 @@
|
|
1
|
-
#
|
2
|
-
[Gush](https://github.com/chaps-io/gush)
|
1
|
+
# DWF
|
2
|
+
Distributed workflow runner following [Gush](https://github.com/chaps-io/gush) interface using [Sidekiq](https://github.com/mperham/sidekiq) and [Redis](https://redis.io/). This project is for researching DSL purpose
|
3
3
|
|
4
4
|
# Installation
|
5
5
|
## 1. Add `dwf` to Gemfile
|
6
6
|
```ruby
|
7
|
-
gem 'dwf', '~> 0.1.
|
7
|
+
gem 'dwf', '~> 0.1.12'
|
8
8
|
```
|
9
|
-
## 2. Execute flow
|
9
|
+
## 2. Execute flow example
|
10
10
|
### Declare jobs
|
11
11
|
|
12
12
|
```ruby
|
13
13
|
require 'dwf'
|
14
14
|
|
15
|
-
class
|
15
|
+
class FirstItem < Dwf::Item
|
16
16
|
def perform
|
17
|
-
puts "#{self.class.name}
|
18
|
-
|
19
|
-
puts params
|
20
|
-
puts "#{self.class.name} Finished"
|
17
|
+
puts "#{self.class.name}: running"
|
18
|
+
puts "#{self.class.name}: finish"
|
21
19
|
end
|
22
20
|
end
|
21
|
+
|
22
|
+
class SecondItem < Dwf::Item
|
23
|
+
def perform
|
24
|
+
puts "#{self.class.name}: running"
|
25
|
+
output('Send to ThirdItem')
|
26
|
+
puts "#{self.class.name} finish"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ThirdItem < Dwf::Item
|
31
|
+
def perform
|
32
|
+
puts "#{self.class.name}: running"
|
33
|
+
puts "#{self.class.name}: finish"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class FourthItem < Dwf::Item
|
38
|
+
def perform
|
39
|
+
puts "#{self.class.name}: running"
|
40
|
+
puts "payloads from incoming: #{payloads.inspect}"
|
41
|
+
puts "#{self.class.name}: finish"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
FifthItem = Class.new(FirstItem)
|
23
46
|
```
|
24
47
|
|
25
48
|
### Declare flow
|
@@ -28,20 +51,23 @@ require 'dwf'
|
|
28
51
|
|
29
52
|
class TestWf < Dwf::Workflow
|
30
53
|
def configure
|
31
|
-
run
|
32
|
-
run
|
33
|
-
run
|
34
|
-
run
|
35
|
-
run
|
36
|
-
run F, params: 'F say hello'
|
54
|
+
run FirstItem
|
55
|
+
run SecondItem, after: FirstItem
|
56
|
+
run ThirdItem, after: FirstItem
|
57
|
+
run FourthItem, after: [ThirdItem, SecondItem]
|
58
|
+
run FifthItem, after: FourthItem
|
37
59
|
end
|
38
60
|
end
|
39
61
|
```
|
40
|
-
|
62
|
+
### Start background worker process
|
63
|
+
```
|
64
|
+
bundle exec sidekiq -q dwf
|
65
|
+
```
|
41
66
|
|
42
67
|
### Execute flow
|
43
68
|
```ruby
|
44
|
-
wf = TestWf.create
|
69
|
+
wf = TestWf.create
|
70
|
+
wf.callback_type = Dwf::Workflow::SK_BATCH
|
45
71
|
wf.start!
|
46
72
|
```
|
47
73
|
|
@@ -54,21 +80,16 @@ By default `dwf` will use `Dwf::Workflow::BUILD_IN` callback.
|
|
54
80
|
|
55
81
|
### Output
|
56
82
|
```
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
E say hello
|
68
|
-
E Finished
|
69
|
-
D Working
|
70
|
-
D say hello
|
71
|
-
D Finished
|
83
|
+
FirstItem: running
|
84
|
+
FirstItem: finish
|
85
|
+
SecondItem: running
|
86
|
+
SecondItem finish
|
87
|
+
ThirdItem: running
|
88
|
+
ThirdItem: finish
|
89
|
+
FourthItem: running
|
90
|
+
FourthItem: finish
|
91
|
+
FifthItem: running
|
92
|
+
FifthItem: finish
|
72
93
|
```
|
73
94
|
|
74
95
|
# Config redis and default queue
|
@@ -83,8 +104,8 @@ Dwf.config do |config|
|
|
83
104
|
config.namespace = 'dwf'
|
84
105
|
end
|
85
106
|
```
|
86
|
-
|
87
|
-
|
107
|
+
# Advanced features
|
108
|
+
## Pipelining
|
88
109
|
You can pass jobs result to next nodes
|
89
110
|
|
90
111
|
```ruby
|
@@ -117,6 +138,96 @@ end
|
|
117
138
|
}
|
118
139
|
]
|
119
140
|
```
|
141
|
+
## Sub workflow
|
142
|
+
There might be a case when you want to reuse a workflow in another workflow
|
143
|
+
|
144
|
+
As an example, let's write a workflow which contain another workflow, expected that the SubWorkflow workflow execute after `SecondItem` and the `ThirdItem` execute after `SubWorkflow`
|
145
|
+
|
146
|
+
### Setup
|
147
|
+
```ruby
|
148
|
+
class FirstItem < Dwf::Item
|
149
|
+
def perform
|
150
|
+
puts "Main flow: #{self.class.name} running"
|
151
|
+
puts "Main flow: #{self.class.name} finish"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
SecondItem = Class.new(FirstItem)
|
156
|
+
ThirtItem = Class.new(FirstItem)
|
157
|
+
|
158
|
+
class FirstSubItem < Dwf::Item
|
159
|
+
def perform
|
160
|
+
puts "Sub flow: #{self.class.name} running"
|
161
|
+
puts "Sub flow: #{self.class.name} finish"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
SecondSubItem = Class.new(FirstSubItem)
|
166
|
+
|
167
|
+
class SubWorkflow < Dwf::Workflow
|
168
|
+
def configure
|
169
|
+
run FirstSubItem
|
170
|
+
run SecondSubItem, after: FirstSubItem
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
class TestWf < Dwf::Workflow
|
176
|
+
def configure
|
177
|
+
run FirstItem
|
178
|
+
run SecondItem, after: FirstItem
|
179
|
+
run SubWorkflow, after: SecondItem
|
180
|
+
run ThirtItem, after: SubWorkflow
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
wf = TestWf.create
|
185
|
+
wf.start!
|
186
|
+
```
|
187
|
+
|
188
|
+
### Result
|
189
|
+
```
|
190
|
+
Main flow: FirstItem running
|
191
|
+
Main flow: FirstItem finish
|
192
|
+
Main flow: SecondItem running
|
193
|
+
Main flow: SecondItem finish
|
194
|
+
Sub flow: FirstSubItem running
|
195
|
+
Sub flow: FirstSubItem finish
|
196
|
+
Sub flow: SecondSubItem running
|
197
|
+
Sub flow: SecondSubItem finish
|
198
|
+
Main flow: ThirtItem running
|
199
|
+
Main flow: ThirtItem finish
|
200
|
+
```
|
201
|
+
|
202
|
+
## Dynamic workflows
|
203
|
+
There might be a case when you have to contruct the workflow dynamically depend on the input
|
204
|
+
As an example, let's write a workflow which puts from 1 to 100 into the terminal parallelly . Additionally after finish all job, it will puts the finshed word into the terminal
|
205
|
+
```ruby
|
206
|
+
class FirstMainItem < Dwf::Item
|
207
|
+
def perform
|
208
|
+
puts "#{self.class.name}: running #{params}"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
SecondMainItem = Class.new(FirstMainItem)
|
213
|
+
|
214
|
+
class TestWf < Dwf::Workflow
|
215
|
+
def configure
|
216
|
+
items = (1..100).to_a.map do |number|
|
217
|
+
run FirstMainItem, params: number
|
218
|
+
end
|
219
|
+
run SecondMainItem, after: items, params: "finished"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
```
|
224
|
+
We can achieve that because run method returns the id of the created job, which we can use for chaining dependencies.
|
225
|
+
Now, when we create the workflow like this:
|
226
|
+
```ruby
|
227
|
+
wf = TestWf.create
|
228
|
+
# wf.callback_type = Dwf::Workflow::SK_BATCH
|
229
|
+
wf.start!
|
230
|
+
```
|
120
231
|
|
121
232
|
# Todo
|
122
233
|
- [x] Make it work
|
@@ -124,9 +235,12 @@ end
|
|
124
235
|
- [x] Support with build-in callback
|
125
236
|
- [x] Add github workflow
|
126
237
|
- [x] Redis configurable
|
127
|
-
- [x]
|
128
|
-
- [
|
238
|
+
- [x] Pipelining
|
239
|
+
- [X] Test
|
240
|
+
- [x] Sub workflow
|
129
241
|
- [ ] Support [Resque](https://github.com/resque/resque)
|
242
|
+
- [ ] Key value store plugable
|
243
|
+
- [ ] research https://github.com/moneta-rb/moneta
|
130
244
|
|
131
245
|
# References
|
132
246
|
- https://github.com/chaps-io/gush
|
data/dwf.gemspec
CHANGED
@@ -3,14 +3,15 @@
|
|
3
3
|
|
4
4
|
lib = File.expand_path('../lib', __FILE__)
|
5
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require_relative 'lib/dwf/version'
|
6
7
|
|
7
8
|
Gem::Specification.new do |spec|
|
8
|
-
spec.name =
|
9
|
-
spec.version =
|
10
|
-
spec.authors = [
|
11
|
-
spec.email = [
|
9
|
+
spec.name = 'dwf'
|
10
|
+
spec.version = Dwf::VERSION
|
11
|
+
spec.authors = ['dthtien']
|
12
|
+
spec.email = ['tiendt2311@gmail.com']
|
12
13
|
|
13
|
-
spec.summary = '
|
14
|
+
spec.summary = 'Distributed workflow runner following Gush interface using Sidekiq and Redis'
|
14
15
|
spec.description = 'Workflow'
|
15
16
|
spec.homepage = 'https://github.com/dthtien/wf'
|
16
17
|
spec.license = "MIT"
|
@@ -25,8 +26,10 @@ Gem::Specification.new do |spec|
|
|
25
26
|
# guide at: https://bundler.io/guides/creating_gem.html
|
26
27
|
|
27
28
|
spec.add_development_dependency 'byebug', '~> 11.1.3'
|
29
|
+
spec.add_development_dependency 'mock_redis', '~> 0.27.2'
|
28
30
|
spec.add_dependency 'redis', '~> 4.2.0'
|
31
|
+
spec.add_dependency 'redis-mutex', '~> 4.0.2'
|
29
32
|
spec.add_development_dependency 'rspec', '~> 3.2'
|
30
|
-
spec.add_development_dependency 'mock_redis', '~> 0.27.2'
|
31
33
|
spec.add_dependency 'sidekiq', '~> 6.2.0'
|
34
|
+
spec.add_development_dependency 'simplecov'
|
32
35
|
end
|
data/lib/dwf/callback.rb
CHANGED
@@ -9,8 +9,8 @@ module Dwf
|
|
9
9
|
previous_job_names = options['names']
|
10
10
|
workflow_id = options['workflow_id']
|
11
11
|
processing_job_names = previous_job_names.map do |job_name|
|
12
|
-
|
13
|
-
|
12
|
+
node = client.find_node(job_name, workflow_id)
|
13
|
+
node.outgoing
|
14
14
|
end.flatten.uniq
|
15
15
|
return if processing_job_names.empty?
|
16
16
|
|
@@ -19,7 +19,7 @@ module Dwf
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def start(job)
|
22
|
-
job.outgoing.any? ? start_with_batch(job) : job.
|
22
|
+
job.outgoing.any? ? start_with_batch(job) : job.persist_and_perform_async!
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
@@ -40,11 +40,14 @@ module Dwf
|
|
40
40
|
batch.on(
|
41
41
|
:success,
|
42
42
|
'Dwf::Callback#process_next_step',
|
43
|
-
names: jobs.map(&:
|
43
|
+
names: jobs.map(&:name),
|
44
44
|
workflow_id: workflow_id
|
45
45
|
)
|
46
46
|
batch.jobs do
|
47
|
-
jobs.each
|
47
|
+
jobs.each do |job|
|
48
|
+
job.reload
|
49
|
+
job.persist_and_perform_async! if job.ready_to_start?
|
50
|
+
end
|
48
51
|
end
|
49
52
|
end
|
50
53
|
|
@@ -61,25 +64,24 @@ module Dwf
|
|
61
64
|
|
62
65
|
def fetch_jobs(processing_job_names, workflow_id)
|
63
66
|
processing_job_names.map do |job_name|
|
64
|
-
client.
|
67
|
+
client.find_node(job_name, workflow_id)
|
65
68
|
end.compact
|
66
69
|
end
|
67
70
|
|
68
|
-
def with_lock(workflow_id, job_name)
|
69
|
-
client.check_or_lock(workflow_id, job_name)
|
70
|
-
yield
|
71
|
-
client.release_lock(workflow_id, job_name)
|
71
|
+
def with_lock(workflow_id, job_name, &block)
|
72
|
+
client.check_or_lock(workflow_id, job_name, &block)
|
72
73
|
end
|
73
74
|
|
74
|
-
def start_with_batch(
|
75
|
+
def start_with_batch(node)
|
75
76
|
batch = Sidekiq::Batch.new
|
77
|
+
workflow_id = node.is_a?(Dwf::Workflow) ? node.parent_id : node.workflow_id
|
76
78
|
batch.on(
|
77
79
|
:success,
|
78
80
|
'Dwf::Callback#process_next_step',
|
79
|
-
names: [
|
80
|
-
workflow_id:
|
81
|
+
names: [node.name],
|
82
|
+
workflow_id: workflow_id
|
81
83
|
)
|
82
|
-
batch.jobs {
|
84
|
+
batch.jobs { node.persist_and_perform_async! }
|
83
85
|
end
|
84
86
|
|
85
87
|
def client
|
data/lib/dwf/client.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require_relative 'errors'
|
2
|
+
require 'redis-mutex'
|
3
|
+
|
1
4
|
module Dwf
|
2
5
|
class Client
|
3
6
|
attr_reader :config
|
@@ -20,18 +23,50 @@ module Dwf
|
|
20
23
|
Dwf::Item.from_hash(Dwf::Utils.symbolize_keys(data))
|
21
24
|
end
|
22
25
|
|
26
|
+
def find_node(name, workflow_id)
|
27
|
+
if Utils.workflow_name?(name)
|
28
|
+
if name.include?('|')
|
29
|
+
_, id = name.split('|')
|
30
|
+
else
|
31
|
+
id = workflow_id(name, workflow_id)
|
32
|
+
end
|
33
|
+
find_workflow(id)
|
34
|
+
else
|
35
|
+
find_job(workflow_id, name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_workflow(id)
|
40
|
+
key = redis.keys("dwf.workflows.#{id}*").first
|
41
|
+
data = redis.get(key)
|
42
|
+
raise WorkflowNotFound, "Workflow with given id doesn't exist" if data.nil?
|
43
|
+
|
44
|
+
hash = JSON.parse(data)
|
45
|
+
hash = Dwf::Utils.symbolize_keys(hash)
|
46
|
+
nodes = parse_nodes(id)
|
47
|
+
workflow_from_hash(hash, nodes)
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_sub_workflow(name, parent_id)
|
51
|
+
find_workflow(workflow_id(name, parent_id))
|
52
|
+
end
|
53
|
+
|
54
|
+
def sub_workflows(id)
|
55
|
+
keys = redis.keys("dwf.workflows.*.*.#{id}")
|
56
|
+
keys.map do |key|
|
57
|
+
id = key.split('.')[2]
|
58
|
+
|
59
|
+
find_workflow(id)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
23
63
|
def persist_job(job)
|
24
64
|
redis.hset("dwf.jobs.#{job.workflow_id}.#{job.klass}", job.id, job.as_json)
|
25
65
|
end
|
26
66
|
|
27
|
-
def check_or_lock(workflow_id, job_name)
|
67
|
+
def check_or_lock(workflow_id, job_name, &block)
|
28
68
|
key = "wf_enqueue_outgoing_jobs_#{workflow_id}-#{job_name}"
|
29
|
-
|
30
|
-
if key_exists?(key)
|
31
|
-
sleep 2
|
32
|
-
else
|
33
|
-
set(key, 'running')
|
34
|
-
end
|
69
|
+
RedisMutex.with_lock(key, sleep: 0.3, block: 2, &block)
|
35
70
|
end
|
36
71
|
|
37
72
|
def release_lock(workflow_id, job_name)
|
@@ -39,7 +74,10 @@ module Dwf
|
|
39
74
|
end
|
40
75
|
|
41
76
|
def persist_workflow(workflow)
|
42
|
-
|
77
|
+
key = [
|
78
|
+
'dwf', 'workflows', workflow.id, workflow.class.name, workflow.parent_id
|
79
|
+
].compact.join('.')
|
80
|
+
redis.set(key, workflow.as_json)
|
43
81
|
end
|
44
82
|
|
45
83
|
def build_job_id(workflow_id, job_klass)
|
@@ -84,6 +122,13 @@ module Dwf
|
|
84
122
|
|
85
123
|
private
|
86
124
|
|
125
|
+
def workflow_id(name, parent_id)
|
126
|
+
key = redis.keys("dwf.workflows.*.#{name}.#{parent_id}").first
|
127
|
+
return if key.nil?
|
128
|
+
|
129
|
+
key.split('.')[2]
|
130
|
+
end
|
131
|
+
|
87
132
|
def find_job_by_klass_and_id(workflow_id, job_name)
|
88
133
|
job_klass, job_id = job_name.split('|')
|
89
134
|
|
@@ -99,8 +144,36 @@ module Dwf
|
|
99
144
|
job
|
100
145
|
end
|
101
146
|
|
147
|
+
def parse_nodes(id)
|
148
|
+
keys = redis.scan_each(match: "dwf.jobs.#{id}.*")
|
149
|
+
|
150
|
+
items = keys.map do |key|
|
151
|
+
redis.hvals(key).map do |json|
|
152
|
+
node = Dwf::Utils.symbolize_keys JSON.parse(json)
|
153
|
+
Dwf::Item.from_hash(node)
|
154
|
+
end
|
155
|
+
end.flatten
|
156
|
+
workflows = sub_workflows(id)
|
157
|
+
items + workflows
|
158
|
+
end
|
159
|
+
|
160
|
+
def workflow_from_hash(hash, jobs = [])
|
161
|
+
flow = Module.const_get(hash[:klass]).new(*hash[:arguments])
|
162
|
+
flow.jobs = []
|
163
|
+
flow.outgoing = hash.fetch(:outgoing, [])
|
164
|
+
flow.parent_id = hash[:parent_id]
|
165
|
+
flow.incoming = hash.fetch(:incoming, [])
|
166
|
+
flow.stopped = hash.fetch(:stopped, false)
|
167
|
+
flow.callback_type = hash.fetch(:callback_type, Workflow::BUILD_IN)
|
168
|
+
flow.id = hash[:id]
|
169
|
+
flow.jobs = jobs
|
170
|
+
flow
|
171
|
+
end
|
172
|
+
|
102
173
|
def redis
|
103
|
-
@redis ||= Redis.new(config.redis_opts)
|
174
|
+
@redis ||= Redis.new(config.redis_opts).tap do |instance|
|
175
|
+
RedisClassy.redis = instance
|
176
|
+
end
|
104
177
|
end
|
105
178
|
end
|
106
179
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Dwf
|
2
|
+
module Concerns
|
3
|
+
module Checkable
|
4
|
+
def no_dependencies?
|
5
|
+
incoming.empty?
|
6
|
+
end
|
7
|
+
|
8
|
+
def leaf?
|
9
|
+
outgoing.empty?
|
10
|
+
end
|
11
|
+
|
12
|
+
def ready_to_start?
|
13
|
+
!running? && !enqueued? && !finished? && !failed? && parents_succeeded?
|
14
|
+
end
|
15
|
+
|
16
|
+
def succeeded?
|
17
|
+
finished? && !failed?
|
18
|
+
end
|
19
|
+
|
20
|
+
def running?
|
21
|
+
started? && !finished?
|
22
|
+
end
|
23
|
+
|
24
|
+
def started?
|
25
|
+
!!started_at
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/dwf/errors.rb
ADDED