dwf 0.1.10 → 0.1.11
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/.gitignore +1 -0
- data/CHANGELOG.md +67 -0
- data/README.md +117 -33
- data/dwf.gemspec +4 -2
- data/lib/dwf/callback.rb +13 -10
- data/lib/dwf/client.rb +50 -9
- data/lib/dwf/concerns/checkable.rb +29 -0
- data/lib/dwf/errors.rb +3 -1
- data/lib/dwf/item.rb +41 -33
- data/lib/dwf/utils.rb +6 -0
- data/lib/dwf/version.rb +1 -1
- data/lib/dwf/workflow.rb +119 -24
- data/spec/dwf/client_spec.rb +77 -3
- data/spec/dwf/item_spec.rb +49 -10
- data/spec/dwf/utils_spec.rb +9 -0
- data/spec/dwf/workflow_spec.rb +178 -13
- data/spec/spec_helper.rb +3 -0
- metadata +27 -12
data/lib/dwf/workflow.rb
CHANGED
@@ -2,16 +2,20 @@
|
|
2
2
|
|
3
3
|
require_relative 'client'
|
4
4
|
require_relative 'worker'
|
5
|
+
require_relative 'concerns/checkable'
|
5
6
|
require_relative 'callback'
|
6
7
|
|
7
8
|
module Dwf
|
8
9
|
class Workflow
|
10
|
+
include Concerns::Checkable
|
11
|
+
|
9
12
|
CALLBACK_TYPES = [
|
10
13
|
BUILD_IN = 'build-in',
|
11
14
|
SK_BATCH = 'sk-batch'
|
12
15
|
].freeze
|
13
|
-
attr_accessor :jobs, :
|
14
|
-
attr_reader :dependencies, :started_at, :finished_at, :persisted, :arguments
|
16
|
+
attr_accessor :jobs, :stopped, :id, :incoming, :outgoing, :parent_id
|
17
|
+
attr_reader :dependencies, :started_at, :finished_at, :persisted, :arguments, :klass,
|
18
|
+
:callback_type
|
15
19
|
|
16
20
|
class << self
|
17
21
|
def create(*args)
|
@@ -31,8 +35,12 @@ module Dwf
|
|
31
35
|
@jobs = []
|
32
36
|
@persisted = false
|
33
37
|
@stopped = false
|
34
|
-
@arguments = args
|
38
|
+
@arguments = *args
|
39
|
+
@parent_id = nil
|
40
|
+
@klass = self.class
|
35
41
|
@callback_type = BUILD_IN
|
42
|
+
@incoming = []
|
43
|
+
@outgoing = []
|
36
44
|
|
37
45
|
setup
|
38
46
|
end
|
@@ -44,16 +52,42 @@ module Dwf
|
|
44
52
|
true
|
45
53
|
end
|
46
54
|
|
55
|
+
def name
|
56
|
+
"#{self.class.name}|#{id}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def sub_workflow?
|
60
|
+
!parent_id.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def callback_type=(type = BUILD_IN)
|
64
|
+
@callback_type = type
|
65
|
+
jobs.each { |job| job.callback_type = type }
|
66
|
+
end
|
67
|
+
|
47
68
|
alias save persist!
|
48
69
|
|
49
70
|
def start!
|
71
|
+
raise UnsupportCallback, 'Sub workflow only works with Sidekiq batch callback' if invalid_callback?
|
72
|
+
|
50
73
|
mark_as_started
|
51
74
|
persist!
|
52
75
|
initial_jobs.each do |job|
|
53
|
-
|
76
|
+
job.payloads = payloads if sub_workflow?
|
77
|
+
job.start_initial!
|
54
78
|
end
|
55
79
|
end
|
56
80
|
|
81
|
+
def payloads
|
82
|
+
@payloads ||= build_payloads
|
83
|
+
end
|
84
|
+
|
85
|
+
def start_initial!
|
86
|
+
cb_build_in? ? start! : Callback.new.start(self)
|
87
|
+
end
|
88
|
+
|
89
|
+
alias persist_and_perform_async! start!
|
90
|
+
|
57
91
|
def reload
|
58
92
|
flow = self.class.find(id)
|
59
93
|
self.stopped = flow.stopped
|
@@ -73,14 +107,7 @@ module Dwf
|
|
73
107
|
def configure(*arguments); end
|
74
108
|
|
75
109
|
def run(klass, options = {})
|
76
|
-
node = klass
|
77
|
-
workflow_id: id,
|
78
|
-
id: client.build_job_id(id, klass.to_s),
|
79
|
-
params: options.fetch(:params, {}),
|
80
|
-
queue: options[:queue],
|
81
|
-
callback_type: callback_type
|
82
|
-
)
|
83
|
-
|
110
|
+
node = build_node(klass, options)
|
84
111
|
jobs << node
|
85
112
|
|
86
113
|
build_dependencies_structure(node, options)
|
@@ -112,7 +139,10 @@ module Dwf
|
|
112
139
|
stopped: stopped,
|
113
140
|
started_at: started_at,
|
114
141
|
finished_at: finished_at,
|
115
|
-
callback_type: callback_type
|
142
|
+
callback_type: callback_type,
|
143
|
+
incoming: incoming,
|
144
|
+
outgoing: outgoing,
|
145
|
+
parent_id: parent_id
|
116
146
|
}
|
117
147
|
end
|
118
148
|
|
@@ -124,13 +154,7 @@ module Dwf
|
|
124
154
|
jobs.all?(&:finished?)
|
125
155
|
end
|
126
156
|
|
127
|
-
|
128
|
-
!!started_at
|
129
|
-
end
|
130
|
-
|
131
|
-
def running?
|
132
|
-
started? && !finished?
|
133
|
-
end
|
157
|
+
alias enqueued? started?
|
134
158
|
|
135
159
|
def failed?
|
136
160
|
jobs.any?(&:failed?)
|
@@ -140,6 +164,10 @@ module Dwf
|
|
140
164
|
stopped
|
141
165
|
end
|
142
166
|
|
167
|
+
def parents_succeeded?
|
168
|
+
incoming.all? { |name| client.find_node(name, parent_id).succeeded? }
|
169
|
+
end
|
170
|
+
|
143
171
|
def status
|
144
172
|
return :failed if failed?
|
145
173
|
return :running if running?
|
@@ -157,8 +185,39 @@ module Dwf
|
|
157
185
|
@stopped = false
|
158
186
|
end
|
159
187
|
|
188
|
+
def leaf_nodes
|
189
|
+
jobs.select(&:leaf?)
|
190
|
+
end
|
191
|
+
|
192
|
+
def output_payload
|
193
|
+
leaf_nodes.map do |node|
|
194
|
+
data = node.output_payload
|
195
|
+
next if data.nil?
|
196
|
+
|
197
|
+
data
|
198
|
+
end.compact
|
199
|
+
end
|
200
|
+
|
160
201
|
private
|
161
202
|
|
203
|
+
def build_node(klass, options)
|
204
|
+
if klass < Dwf::Workflow
|
205
|
+
node = options[:params].nil? ? klass.new : klass.new(options[:params])
|
206
|
+
node.parent_id = id
|
207
|
+
node.callback_type = SK_BATCH
|
208
|
+
node.save
|
209
|
+
node
|
210
|
+
else
|
211
|
+
klass.new(
|
212
|
+
workflow_id: id,
|
213
|
+
id: client.build_job_id(id, klass.to_s),
|
214
|
+
params: options.fetch(:params, {}),
|
215
|
+
queue: options[:queue],
|
216
|
+
callback_type: callback_type
|
217
|
+
)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
162
221
|
def initial_jobs
|
163
222
|
jobs.select(&:no_dependencies?)
|
164
223
|
end
|
@@ -168,16 +227,52 @@ module Dwf
|
|
168
227
|
resolve_dependencies
|
169
228
|
end
|
170
229
|
|
230
|
+
def find_node(node_name)
|
231
|
+
if Utils.workflow_name?(node_name)
|
232
|
+
find_subworkflow(node_name)
|
233
|
+
else
|
234
|
+
find_job(node_name)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def find_subworkflow(node_name)
|
239
|
+
fname, = node_name.split('|')
|
240
|
+
jobs.find { |j| j.klass.name == fname }
|
241
|
+
end
|
242
|
+
|
171
243
|
def resolve_dependencies
|
172
244
|
@dependencies.each do |dependency|
|
173
|
-
from =
|
174
|
-
to =
|
245
|
+
from = find_node(dependency[:from])
|
246
|
+
to = find_node(dependency[:to])
|
175
247
|
|
176
|
-
to.incoming <<
|
177
|
-
from.outgoing <<
|
248
|
+
to.incoming << from.name
|
249
|
+
from.outgoing << to.name
|
178
250
|
end
|
179
251
|
end
|
180
252
|
|
253
|
+
def invalid_callback?
|
254
|
+
cb_build_in? && jobs.any? { |job| job.class < Workflow }
|
255
|
+
end
|
256
|
+
|
257
|
+
def build_payloads
|
258
|
+
return unless sub_workflow?
|
259
|
+
|
260
|
+
data = incoming.map do |job_name|
|
261
|
+
next if Utils.workflow_name?(job_name)
|
262
|
+
|
263
|
+
node = client.find_node(job_name, parent_id)
|
264
|
+
next if node.output_payload.nil?
|
265
|
+
|
266
|
+
{
|
267
|
+
id: node.name,
|
268
|
+
class: node.klass.to_s,
|
269
|
+
output: node.output_payload
|
270
|
+
}
|
271
|
+
end.compact
|
272
|
+
|
273
|
+
data.empty? ? nil : data
|
274
|
+
end
|
275
|
+
|
181
276
|
def build_dependencies_structure(node, options)
|
182
277
|
deps_after = [*options[:after]]
|
183
278
|
|
data/spec/dwf/client_spec.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
require 'mock_redis'
|
5
|
+
FirstWorkflow = Class.new(Dwf::Workflow)
|
6
|
+
SecondWorkflow = Class.new(Dwf::Workflow)
|
5
7
|
|
6
8
|
describe Dwf::Client, client: true do
|
7
9
|
let(:client) { described_class.new }
|
@@ -30,12 +32,12 @@ describe Dwf::Client, client: true do
|
|
30
32
|
end
|
31
33
|
|
32
34
|
context 'find by item name' do
|
33
|
-
it
|
35
|
+
it do
|
34
36
|
item = client.find_job(workflow_id, job.name)
|
35
37
|
expect(item.workflow_id).to eq workflow_id
|
36
38
|
expect(item.id).to eq id
|
37
39
|
expect(item.name).to eq job.name
|
38
|
-
|
40
|
+
end
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
@@ -62,6 +64,47 @@ describe Dwf::Client, client: true do
|
|
62
64
|
end
|
63
65
|
end
|
64
66
|
|
67
|
+
describe '#find_node' do
|
68
|
+
context 'find job' do
|
69
|
+
let!(:job) do
|
70
|
+
j = Dwf::Item.new(workflow_id: workflow_id, id: id)
|
71
|
+
j.persist!
|
72
|
+
j
|
73
|
+
end
|
74
|
+
|
75
|
+
it do
|
76
|
+
item = client.find_node(Dwf::Item.name, workflow_id)
|
77
|
+
expect(item.workflow_id).to eq workflow_id
|
78
|
+
expect(item.id).to eq id
|
79
|
+
expect(item.name).to eq job.name
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'find_workflow' do
|
84
|
+
let!(:wf1) { FirstWorkflow.create }
|
85
|
+
let!(:wf2) do
|
86
|
+
wf = SecondWorkflow.new
|
87
|
+
wf.parent_id = wf1.id
|
88
|
+
wf.save
|
89
|
+
wf
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'find with class name and parent id' do
|
93
|
+
it do
|
94
|
+
wf = client.find_node(wf2.class.name, wf1.id)
|
95
|
+
expect(wf).to be_kind_of(SecondWorkflow)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'find with name and parent id' do
|
100
|
+
it do
|
101
|
+
wf = client.find_node(wf2.name, wf1.id)
|
102
|
+
expect(wf).to be_kind_of(SecondWorkflow)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
65
108
|
describe '#persist_job' do
|
66
109
|
let!(:job) { Dwf::Item.new(workflow_id: workflow_id, id: id) }
|
67
110
|
|
@@ -76,13 +119,44 @@ describe Dwf::Client, client: true do
|
|
76
119
|
end
|
77
120
|
end
|
78
121
|
|
122
|
+
describe '#find_sub_workflow' do
|
123
|
+
let!(:wf1) { FirstWorkflow.create }
|
124
|
+
let!(:wf2) do
|
125
|
+
wf = SecondWorkflow.new
|
126
|
+
wf.parent_id = wf1.id
|
127
|
+
wf.save
|
128
|
+
wf
|
129
|
+
end
|
130
|
+
|
131
|
+
it do
|
132
|
+
wf = client.find_sub_workflow(wf2.class.name, wf1.id)
|
133
|
+
expect(wf).to be_kind_of(SecondWorkflow)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe '#sub_workflows' do
|
138
|
+
let!(:wf1) { FirstWorkflow.create }
|
139
|
+
let!(:wf2) do
|
140
|
+
wf = SecondWorkflow.new
|
141
|
+
wf.parent_id = wf1.id
|
142
|
+
wf.save
|
143
|
+
wf
|
144
|
+
end
|
145
|
+
|
146
|
+
it do
|
147
|
+
wfs = client.sub_workflows(wf1.id)
|
148
|
+
expect(wfs).not_to be_empty
|
149
|
+
expect(wfs.first).to be_kind_of(SecondWorkflow)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
79
153
|
describe '#persist_workflow' do
|
80
154
|
let(:workflow) { Dwf::Workflow.new }
|
81
155
|
|
82
156
|
it do
|
83
157
|
expect(redis.exists?("dwf.workflows.#{workflow.id}")).to be_falsy
|
84
158
|
client.persist_workflow(workflow)
|
85
|
-
expect(redis.
|
159
|
+
expect(redis.keys("dwf.workflows.#{workflow.id}*").any?).to be_truthy
|
86
160
|
end
|
87
161
|
end
|
88
162
|
|
data/spec/dwf/item_spec.rb
CHANGED
@@ -20,7 +20,8 @@ describe Dwf::Item, item: true do
|
|
20
20
|
klass: 'Dwf::Item',
|
21
21
|
started_at: started_at,
|
22
22
|
finished_at: finished_at,
|
23
|
-
callback_type: Dwf::Workflow::BUILD_IN
|
23
|
+
callback_type: Dwf::Workflow::BUILD_IN,
|
24
|
+
payloads: nil
|
24
25
|
}
|
25
26
|
end
|
26
27
|
let!(:item) { described_class.new(options) }
|
@@ -89,7 +90,7 @@ describe Dwf::Item, item: true do
|
|
89
90
|
before do
|
90
91
|
allow(Dwf::Client).to receive(:new).and_return client_double
|
91
92
|
allow(client_double)
|
92
|
-
.to receive(:
|
93
|
+
.to receive(:find_node).and_return a_item
|
93
94
|
end
|
94
95
|
|
95
96
|
context 'parent jobs already finished' do
|
@@ -98,8 +99,8 @@ describe Dwf::Item, item: true do
|
|
98
99
|
it do
|
99
100
|
expect(item.parents_succeeded?).to be_truthy
|
100
101
|
expect(client_double)
|
101
|
-
.to have_received(:
|
102
|
-
.with(
|
102
|
+
.to have_received(:find_node)
|
103
|
+
.with(incoming.first, workflow_id)
|
103
104
|
end
|
104
105
|
end
|
105
106
|
|
@@ -109,8 +110,8 @@ describe Dwf::Item, item: true do
|
|
109
110
|
it do
|
110
111
|
expect(item.parents_succeeded?).to be_falsy
|
111
112
|
expect(client_double)
|
112
|
-
.to have_received(:
|
113
|
-
.with(
|
113
|
+
.to have_received(:find_node)
|
114
|
+
.with(incoming.first, workflow_id)
|
114
115
|
end
|
115
116
|
end
|
116
117
|
end
|
@@ -189,8 +190,9 @@ describe Dwf::Item, item: true do
|
|
189
190
|
end
|
190
191
|
|
191
192
|
describe '#payloads' do
|
192
|
-
let(:incoming) { ["
|
193
|
-
let(:client_double) { double(find_job: nil) }
|
193
|
+
let(:incoming) { ["Dwf::Item|#{SecureRandom.uuid}", "Dwf::Workflow|#{workflow_id}"] }
|
194
|
+
let(:client_double) { double(find_job: nil, build_workflow_id: workflow_id) }
|
195
|
+
let(:workflow) { Dwf::Workflow.new }
|
194
196
|
let!(:a_item) do
|
195
197
|
described_class.new(
|
196
198
|
workflow_id: SecureRandom.uuid,
|
@@ -203,7 +205,11 @@ describe Dwf::Item, item: true do
|
|
203
205
|
before do
|
204
206
|
allow(Dwf::Client).to receive(:new).and_return client_double
|
205
207
|
allow(client_double)
|
206
|
-
.to receive(:
|
208
|
+
.to receive(:find_node)
|
209
|
+
.with(incoming.first, workflow_id).and_return a_item
|
210
|
+
allow(client_double)
|
211
|
+
.to receive(:find_node)
|
212
|
+
.with(incoming.last, workflow_id).and_return workflow
|
207
213
|
end
|
208
214
|
|
209
215
|
it do
|
@@ -212,9 +218,42 @@ describe Dwf::Item, item: true do
|
|
212
218
|
class: a_item.class.name,
|
213
219
|
id: a_item.name,
|
214
220
|
output: 1
|
221
|
+
},
|
222
|
+
{
|
223
|
+
class: workflow.class.name,
|
224
|
+
id: workflow.name,
|
225
|
+
output: []
|
215
226
|
}
|
216
227
|
]
|
217
|
-
expect(item.payloads).to
|
228
|
+
expect(item.payloads).to match_array expected_payload
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe '#start_batch!' do
|
233
|
+
let(:callback_double) { double(start: nil) }
|
234
|
+
let(:client_double) { double(persist_job: nil) }
|
235
|
+
|
236
|
+
before do
|
237
|
+
allow(Dwf::Client).to receive(:new).and_return client_double
|
238
|
+
allow(Dwf::Callback).to receive(:new).and_return callback_double
|
239
|
+
item.start_batch!
|
240
|
+
end
|
241
|
+
|
242
|
+
it do
|
243
|
+
expect(callback_double).to have_received(:start).with(item)
|
244
|
+
expect(item.enqueued_at).not_to be_nil
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe '#leaf?' do
|
249
|
+
context 'when item has outgoing item' do
|
250
|
+
let(:outgoing) { ["Dwf::Item|#{SecureRandom.uuid}"] }
|
251
|
+
it { expect(item.leaf?).to be_falsy }
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'when item does not have outgoing item' do
|
255
|
+
let(:outgoing) { [] }
|
256
|
+
it { expect(item.leaf?).to be_truthy }
|
218
257
|
end
|
219
258
|
end
|
220
259
|
end
|
data/spec/dwf/utils_spec.rb
CHANGED
@@ -20,4 +20,13 @@ describe Dwf::Utils, utils: true do
|
|
20
20
|
|
21
21
|
it { expect(described_class.symbolize_keys(hash)).to eq expected }
|
22
22
|
end
|
23
|
+
|
24
|
+
describe "#workflow_name?" do
|
25
|
+
FirstWorkflow = Class.new(Dwf::Workflow)
|
26
|
+
FirstJob = Class.new(Dwf::Item)
|
27
|
+
|
28
|
+
it { expect(described_class.workflow_name?(Dwf::Workflow.name)).to be_truthy }
|
29
|
+
it { expect(described_class.workflow_name?(FirstWorkflow.name)).to be_truthy }
|
30
|
+
it { expect(described_class.workflow_name?(FirstJob.name)).to be_falsy }
|
31
|
+
end
|
23
32
|
end
|