rworkflow 0.6.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +117 -0
- data/lib/rworkflow/flow.rb +450 -0
- data/lib/rworkflow/flow_registry.rb +69 -0
- data/lib/rworkflow/lifecycle.rb +102 -0
- data/lib/rworkflow/minitest/test.rb +53 -0
- data/lib/rworkflow/minitest/worker.rb +17 -0
- data/lib/rworkflow/minitest.rb +8 -0
- data/lib/rworkflow/sidekiq_flow.rb +186 -0
- data/lib/rworkflow/sidekiq_helper.rb +84 -0
- data/lib/rworkflow/sidekiq_lifecycle.rb +8 -0
- data/lib/rworkflow/sidekiq_state.rb +42 -0
- data/lib/rworkflow/state.rb +104 -0
- data/lib/rworkflow/state_error.rb +13 -0
- data/lib/rworkflow/transition_error.rb +9 -0
- data/lib/rworkflow/version.rb +3 -0
- data/lib/rworkflow/worker.rb +62 -0
- data/lib/rworkflow.rb +15 -0
- data/lib/tasks/rworkflow_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config/application.rb +15 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +83 -0
- data/test/dummy/config/environments/test.rb +41 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +516 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/flow_test.rb +112 -0
- data/test/lifecycle_test.rb +81 -0
- data/test/rworkflow_test.rb +7 -0
- data/test/sidekiq_flow_test.rb +173 -0
- data/test/state_test.rb +99 -0
- data/test/test_helper.rb +32 -0
- metadata +199 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0a21aaa1eb7353e45eb10cf8498ed9c8ae9cebfc
|
4
|
+
data.tar.gz: 05a018eb4b27041ed86515cb7db4e2c945708368
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f304c504e1f537f0430f94739997c858b23d2308844796d00b2bc40fb74be22cdbd0f018d6b004e60e05538e4c3283ba969ac5572ef967257e91c3cd4d5f611d
|
7
|
+
data.tar.gz: d89cddce1e44a96ef6252ff43a528780b7f4c023ee9e83d93bf08d679448704341a61614dec068dc73a1d028279f9c94c44641078bdb40449611426c5b96164b
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Rworkflow'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
Bundler::GemHelper.install_tasks
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
|
21
|
+
Rake::TestTask.new(:test) do |t|
|
22
|
+
t.libs << 'lib'
|
23
|
+
t.libs << 'test'
|
24
|
+
t.pattern = 'test/**/*_test.rb'
|
25
|
+
t.verbose = false
|
26
|
+
end
|
27
|
+
|
28
|
+
task default: :test
|
29
|
+
|
30
|
+
namespace :cim do
|
31
|
+
desc 'Tags, updates README, and CHANGELOG and pushes to Github. Requires ruby-git'
|
32
|
+
task :release do
|
33
|
+
tasks = ['cim:assert_clean_repo', 'cim:git_fetch', 'cim:assert_version', 'cim:update_readme', 'cim:update_changelog', 'cim:commit_changes', 'cim:tag']
|
34
|
+
begin
|
35
|
+
tasks.each { |task| Rake::Task[task].invoke }
|
36
|
+
`git push && git push origin '#{Rworkflow::VERSION}'`
|
37
|
+
rescue => error
|
38
|
+
puts ">>> ERROR: #{error}; might want to reset your repository"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
desc 'Fails if the current repository is not clean'
|
43
|
+
task :assert_clean_repo do
|
44
|
+
status = `git status -s`.chomp.strip
|
45
|
+
if status.empty?
|
46
|
+
status = `git log origin/master..HEAD`.chomp.strip # check if we have unpushed commits
|
47
|
+
if status.empty?
|
48
|
+
puts ">>> Repository is clean!"
|
49
|
+
else
|
50
|
+
puts ">>> Please push your committed changes before releasing!"
|
51
|
+
exit -1
|
52
|
+
end
|
53
|
+
else
|
54
|
+
puts ">>> Please stash or commit your changes before releasing!"
|
55
|
+
exit -1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
desc 'Fetches latest tags/commits'
|
60
|
+
task :git_fetch do
|
61
|
+
puts '>>> Fetching latest git refs'
|
62
|
+
`git fetch --tags`
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'Fails if the current gem version is not greater than the last tag'
|
66
|
+
task :assert_version do
|
67
|
+
latest = `git describe --abbrev=0`.chomp.strip
|
68
|
+
current = Rworkflow::VERSION
|
69
|
+
|
70
|
+
unless Gem::Version.new(current) > Gem::Version.new(latest)
|
71
|
+
puts ">>> Latest tagged version is #{latest}; make sure gem version (#{current}) is greater!"
|
72
|
+
exit -1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
desc 'Updates README with latest version'
|
77
|
+
task :update_readme do
|
78
|
+
puts '>>> Updating README.md'
|
79
|
+
replace = %([](https://github.com/barcoo/rworkflow/releases/tag/#{Rworkflow::VERSION}))
|
80
|
+
|
81
|
+
`sed -i -u 's@^\\[\\!\\[GitHub release\\].*$@#{replace}@' README.md`
|
82
|
+
end
|
83
|
+
|
84
|
+
desc 'Updates CHANGELOG with commit log from last tag to this one'
|
85
|
+
task :update_changelog do
|
86
|
+
puts '>>> Updating CHANGELOG.md'
|
87
|
+
latest = `git describe --abbrev=0`.chomp.strip
|
88
|
+
log = `git log --pretty=format:'- [%h](https://github.com/barcoo/rworkflow/commit/%h) *%ad* __%s__ (%an)' --date=short '#{latest}'..HEAD`.chomp
|
89
|
+
|
90
|
+
changelog = File.open('.CHANGELOG.md', 'w')
|
91
|
+
changelog.write("# Changelog\n\n###{Rworkflow::VERSION}\n\n#{log}\n\n")
|
92
|
+
File.open('CHANGELOG.md', 'r') do |file|
|
93
|
+
file.readline # skip first two lines
|
94
|
+
file.readline
|
95
|
+
while buffer = file.read(2048)
|
96
|
+
changelog.write(buffer)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
changelog.close
|
101
|
+
`mv '.CHANGELOG.md' 'CHANGELOG.md'`
|
102
|
+
end
|
103
|
+
|
104
|
+
desc 'Commits the README/CHANGELOG changes'
|
105
|
+
task :commit_changes do
|
106
|
+
puts '>>> Committing updates to README/CHANGELOG'
|
107
|
+
`git commit -am'Updated README.md and CHANGELOG.md on new release'`
|
108
|
+
end
|
109
|
+
|
110
|
+
desc 'Creates and pushes the tag to git'
|
111
|
+
task :tag do
|
112
|
+
puts '>>> Tagging'
|
113
|
+
message = STDOUT.print ">>> Please enter a tag message: "
|
114
|
+
input = STDIN.gets.strip.tr("'", "\'")
|
115
|
+
`git tag -a '#{Rworkflow::VERSION}' -m '#{input}'`
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,450 @@
|
|
1
|
+
module Rworkflow
|
2
|
+
class Flow
|
3
|
+
STATE_SUCCESSFUL = :successful
|
4
|
+
STATE_FAILED = :failed
|
5
|
+
STATES_TERMINAL = [STATE_FAILED, STATE_SUCCESSFUL].freeze
|
6
|
+
STATES_FAILED = [STATE_FAILED].freeze
|
7
|
+
|
8
|
+
REDIS_NS = 'flow'.freeze
|
9
|
+
WORKFLOW_REGISTRY = "#{REDIS_NS}:__registry".freeze
|
10
|
+
|
11
|
+
attr_accessor :id
|
12
|
+
attr_reader :lifecycle
|
13
|
+
|
14
|
+
def initialize(id)
|
15
|
+
@id = id
|
16
|
+
@redis_key = "#{REDIS_NS}:#{id}"
|
17
|
+
|
18
|
+
@storage = RedisRds::Hash.new(@redis_key)
|
19
|
+
@flow_data = RedisRds::Hash.new("#{@redis_key}__data")
|
20
|
+
@processing = RedisRds::Hash.new("#{@redis_key}__processing")
|
21
|
+
|
22
|
+
load_lifecycle
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_lifecycle
|
26
|
+
serialized = @storage.get(:lifecycle)
|
27
|
+
unless serialized.nil?
|
28
|
+
raw = self.class.serializer.load(serialized)
|
29
|
+
@lifecycle = Rworkflow::Lifecycle.unserialize(raw) unless raw.nil?
|
30
|
+
end
|
31
|
+
rescue
|
32
|
+
@lifecycle = nil
|
33
|
+
end
|
34
|
+
private :load_lifecycle
|
35
|
+
|
36
|
+
def lifecycle=(new_lifecycle)
|
37
|
+
@lifecycle = new_lifecycle
|
38
|
+
@storage.set(:lifecycle, self.class.serializer.dump(@lifecycle.serialize))
|
39
|
+
end
|
40
|
+
|
41
|
+
def finished?
|
42
|
+
return false unless started?
|
43
|
+
total = get_counters.reduce(0) do |sum, pair|
|
44
|
+
self.class.terminal?(pair[0]) ? sum : (sum + pair[1].to_i)
|
45
|
+
end
|
46
|
+
|
47
|
+
return total == 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def status
|
51
|
+
status = 'Running'
|
52
|
+
status = (successful?) ? 'Finished' : 'Failed' if finished?
|
53
|
+
|
54
|
+
return status
|
55
|
+
end
|
56
|
+
|
57
|
+
def created_at
|
58
|
+
return @created_at ||= begin Time.at(get(:created_at, 0)) end
|
59
|
+
end
|
60
|
+
|
61
|
+
def started?
|
62
|
+
return !get(:start_time).nil?
|
63
|
+
end
|
64
|
+
|
65
|
+
def name
|
66
|
+
return get(:name, @id)
|
67
|
+
end
|
68
|
+
|
69
|
+
def name=(name)
|
70
|
+
return set(:name, name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def start_time
|
74
|
+
return Time.at(get(:start_time, 0))
|
75
|
+
end
|
76
|
+
|
77
|
+
def finish_time
|
78
|
+
return Time.at(get(:finish_time, 0))
|
79
|
+
end
|
80
|
+
|
81
|
+
def expected_duration
|
82
|
+
return Float::INFINITY
|
83
|
+
end
|
84
|
+
|
85
|
+
def valid?
|
86
|
+
return !@lifecycle.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
def count(state)
|
90
|
+
return get_list(state).size
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_counters
|
94
|
+
counters = @storage.get(:counters)
|
95
|
+
if !counters.nil?
|
96
|
+
counters = begin
|
97
|
+
self.class.serializer.load(counters)
|
98
|
+
rescue => e
|
99
|
+
Rails.logger.error("Error loading stored flow counters: #{e.message}")
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
return counters || get_counters!
|
104
|
+
end
|
105
|
+
|
106
|
+
# fetches counters atomically
|
107
|
+
def get_counters!
|
108
|
+
counters = { processing: 0 }
|
109
|
+
|
110
|
+
names = @lifecycle.states.keys
|
111
|
+
results = RedisRds::Object.connection.multi do
|
112
|
+
self.class::STATES_TERMINAL.each { |name| get_list(name).size }
|
113
|
+
names.each { |name| get_list(name).size }
|
114
|
+
@processing.getall
|
115
|
+
end
|
116
|
+
|
117
|
+
(self.class::STATES_TERMINAL + names).each do |name|
|
118
|
+
counters[name] = results.shift.to_i
|
119
|
+
end
|
120
|
+
|
121
|
+
counters[:processing] = results.shift.reduce(0) { |sum, pair| sum + pair.last.to_i }
|
122
|
+
|
123
|
+
return counters
|
124
|
+
end
|
125
|
+
private :get_counters!
|
126
|
+
|
127
|
+
def fetch(fetcher_id, state_name)
|
128
|
+
@processing.set(fetcher_id, 1)
|
129
|
+
list = get_state_list(state_name)
|
130
|
+
unless list.nil?
|
131
|
+
failed = []
|
132
|
+
cardinality = @lifecycle.states[state_name].cardinality
|
133
|
+
cardinality = get(:start_count).to_i if cardinality == Lifecycle::CARDINALITY_ALL_STARTED
|
134
|
+
force_list_complete = @lifecycle.states[state_name].policy == State::STATE_POLICY_WAIT
|
135
|
+
raw_objects = list.lpop(cardinality, force_list_complete)
|
136
|
+
unless raw_objects.empty?
|
137
|
+
objects = raw_objects.map do |raw_object|
|
138
|
+
begin
|
139
|
+
self.class.serializer.load(raw_object)
|
140
|
+
rescue StandardError => _
|
141
|
+
failed << raw_object
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
end.compact
|
145
|
+
@processing.set(fetcher_id, objects.size)
|
146
|
+
|
147
|
+
unless failed.empty?
|
148
|
+
push(failed, STATE_FAILED)
|
149
|
+
Rails.logger.error("Failed to parse #{failed.size} in workflow #{@id} for fetcher id #{fetcher_id} at state #{state_name}")
|
150
|
+
end
|
151
|
+
|
152
|
+
yield(objects) if block_given?
|
153
|
+
end
|
154
|
+
end
|
155
|
+
ensure
|
156
|
+
@processing.remove(fetcher_id)
|
157
|
+
terminate if finished?
|
158
|
+
end
|
159
|
+
|
160
|
+
def list_objects(state_name, limit = -1)
|
161
|
+
list = get_list(state_name)
|
162
|
+
return list.get(0, limit).map {|object| self.class.serializer.load(object)}
|
163
|
+
end
|
164
|
+
|
165
|
+
def get_state_list(state_name)
|
166
|
+
list = nil
|
167
|
+
state = @lifecycle.states[state_name]
|
168
|
+
|
169
|
+
if !state.nil?
|
170
|
+
list = get_list(state_name)
|
171
|
+
else
|
172
|
+
Rails.logger.error("Tried accessing invalid state #{state_name} for workflow #{id}")
|
173
|
+
end
|
174
|
+
return list
|
175
|
+
end
|
176
|
+
private :get_state_list
|
177
|
+
|
178
|
+
def terminate
|
179
|
+
mutex = RedisRds::Mutex.new(self.id)
|
180
|
+
mutex.synchronize do
|
181
|
+
if !self.cleaned_up?
|
182
|
+
set(:finish_time, Time.now.to_i)
|
183
|
+
post_process
|
184
|
+
|
185
|
+
if self.public?
|
186
|
+
counters = get_counters!
|
187
|
+
counters[:processing] = 0 # Some worker might have increased the processing flag at that time even if there is no more jobs to be done
|
188
|
+
@storage.setnx(:counters, self.class.serializer.dump(counters))
|
189
|
+
states_cleanup
|
190
|
+
else
|
191
|
+
self.cleanup
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def post_process
|
198
|
+
end
|
199
|
+
protected :post_process
|
200
|
+
|
201
|
+
def metadata_string
|
202
|
+
return "Rworkflow: #{self.name}"
|
203
|
+
end
|
204
|
+
|
205
|
+
def cleaned_up?
|
206
|
+
return states_list.all? { |name| !get_list(name).exists? }
|
207
|
+
end
|
208
|
+
|
209
|
+
def states_list
|
210
|
+
states = self.class::STATES_TERMINAL
|
211
|
+
states += @lifecycle.states.keys if valid?
|
212
|
+
|
213
|
+
return states
|
214
|
+
end
|
215
|
+
|
216
|
+
def transition(from_state, name, objects)
|
217
|
+
objects = Array.wrap(objects)
|
218
|
+
to_state = begin
|
219
|
+
lifecycle.transition(from_state, name)
|
220
|
+
rescue Rworkflow::StateError => e
|
221
|
+
Rails.logger.error("Error transitioning: #{e}")
|
222
|
+
nil
|
223
|
+
end
|
224
|
+
|
225
|
+
if !to_state.nil?
|
226
|
+
push(objects, to_state)
|
227
|
+
log(from_state, name, objects.size)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def logging?
|
232
|
+
return get(:logging, false)
|
233
|
+
end
|
234
|
+
|
235
|
+
def log(from_state, transition, num_objects)
|
236
|
+
logger.incrby("#{from_state}__#{transition}", num_objects.to_i) if logging?
|
237
|
+
end
|
238
|
+
|
239
|
+
def logger
|
240
|
+
return @logger ||= begin
|
241
|
+
RedisRds::Hash.new("#{@redis_key}__logger")
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def logs
|
246
|
+
logs = {}
|
247
|
+
if valid? && logging?
|
248
|
+
state_transition_counters = logger.getall
|
249
|
+
state_transition_counters.each do |state_transition, counter|
|
250
|
+
state, transition = state_transition.split('__')
|
251
|
+
logs[state] = {} unless logs.key?(state)
|
252
|
+
logs[state][transition] = counter.to_i
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
return logs
|
257
|
+
end
|
258
|
+
|
259
|
+
def get_state_cardinality(state_name)
|
260
|
+
cardinality = @lifecycle.states[state_name].cardinality
|
261
|
+
cardinality = self.get(:start_count).to_i if cardinality == Rworkflow::Lifecycle::CARDINALITY_ALL_STARTED
|
262
|
+
return cardinality
|
263
|
+
end
|
264
|
+
|
265
|
+
def set(key, value)
|
266
|
+
@flow_data.set(key, self.class.serializer.dump(value))
|
267
|
+
end
|
268
|
+
|
269
|
+
def get(key, default = nil)
|
270
|
+
value = @flow_data.get(key)
|
271
|
+
value = if value.nil? then default else self.class.serializer.load(value) end
|
272
|
+
|
273
|
+
return value
|
274
|
+
end
|
275
|
+
|
276
|
+
def incr(key, value = 1)
|
277
|
+
return @flow_data.incrby(key, value)
|
278
|
+
end
|
279
|
+
|
280
|
+
def push(objects, state)
|
281
|
+
objects = Array.wrap(objects)
|
282
|
+
|
283
|
+
return 0 if objects.empty?
|
284
|
+
|
285
|
+
list = get_list(state)
|
286
|
+
list.rpush(objects.map { |object| self.class.serializer.dump(object) })
|
287
|
+
|
288
|
+
return objects.size
|
289
|
+
end
|
290
|
+
private :push
|
291
|
+
|
292
|
+
def get_list(name)
|
293
|
+
return RedisRds::List.new("#{@redis_key}:lists:#{name}")
|
294
|
+
end
|
295
|
+
private :get_list
|
296
|
+
|
297
|
+
def cleanup
|
298
|
+
return if Rails.env.test?
|
299
|
+
states_cleanup
|
300
|
+
logger.delete
|
301
|
+
@processing.delete
|
302
|
+
self.class.unregister(self)
|
303
|
+
@flow_data.delete
|
304
|
+
@storage.delete
|
305
|
+
end
|
306
|
+
|
307
|
+
def states_cleanup
|
308
|
+
return if Rails.env.test?
|
309
|
+
states_list.each { |name| get_list(name).delete }
|
310
|
+
end
|
311
|
+
protected :states_cleanup
|
312
|
+
|
313
|
+
def start(objects)
|
314
|
+
objects = Array.wrap(objects)
|
315
|
+
set(:start_time, Time.now.to_i)
|
316
|
+
set(:start_count, objects.size)
|
317
|
+
push(objects, lifecycle.initial)
|
318
|
+
log(lifecycle.initial, 'initial', objects.size)
|
319
|
+
end
|
320
|
+
|
321
|
+
def total_objects_processed(counters = nil)
|
322
|
+
return (counters || get_counters).reduce(0) do |sum, pair|
|
323
|
+
if self.class.terminal?(pair[0])
|
324
|
+
sum + pair[1]
|
325
|
+
else
|
326
|
+
sum
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def total_objects(counters = nil)
|
332
|
+
return (counters || get_counters).reduce(0) { |sum, pair| sum + pair[1] }
|
333
|
+
end
|
334
|
+
|
335
|
+
def total_objects_failed(counters = nil)
|
336
|
+
return (counters || get_counters).reduce(0) do |sum, pair|
|
337
|
+
if self.class.failure?(pair[0])
|
338
|
+
sum + pair[1]
|
339
|
+
else
|
340
|
+
sum
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def successful?
|
346
|
+
return false if !finished?
|
347
|
+
return !failed?
|
348
|
+
end
|
349
|
+
|
350
|
+
def failed?
|
351
|
+
return false if !finished?
|
352
|
+
return total_objects_failed > 0
|
353
|
+
end
|
354
|
+
|
355
|
+
def public?
|
356
|
+
return @public ||= begin get(:public, false) end
|
357
|
+
end
|
358
|
+
|
359
|
+
class << self
|
360
|
+
def create(lifecycle, name = '', options = {})
|
361
|
+
id = generate_id(name)
|
362
|
+
workflow = new(id)
|
363
|
+
workflow.name = name
|
364
|
+
workflow.lifecycle = lifecycle
|
365
|
+
workflow.set(:created_at, Time.now.to_i)
|
366
|
+
workflow.set(:public, options.fetch(:public, false))
|
367
|
+
workflow.set(:logging, options.fetch(:logging, true))
|
368
|
+
|
369
|
+
register(workflow)
|
370
|
+
|
371
|
+
return workflow
|
372
|
+
end
|
373
|
+
|
374
|
+
def generate_id(workflow_name)
|
375
|
+
now = Time.now.to_f
|
376
|
+
random = Random.new(now)
|
377
|
+
return "#{name}__#{workflow_name}__#{(Time.now.to_f * 1000).to_i}__#{random.rand(now).to_i}"
|
378
|
+
end
|
379
|
+
private :generate_id
|
380
|
+
|
381
|
+
def cleanup(id)
|
382
|
+
workflow = new(id)
|
383
|
+
workflow.cleanup
|
384
|
+
end
|
385
|
+
|
386
|
+
def get_public_workflows(options = {})
|
387
|
+
return registry.public_flows(options.reverse_merge(parent_class: self)).map { |id| load(id) }
|
388
|
+
end
|
389
|
+
|
390
|
+
def get_private_workflows(options = {})
|
391
|
+
return registry.private_flows(options.reverse_merge(parent_class: self)).map { |id| load(id) }
|
392
|
+
end
|
393
|
+
|
394
|
+
def all(options = {})
|
395
|
+
return registry.all(options.reverse_merge(parent_class: self)).map { |id| load(id) }
|
396
|
+
end
|
397
|
+
|
398
|
+
def load(id, klass = nil)
|
399
|
+
workflow = nil
|
400
|
+
|
401
|
+
klass = read_flow_class(id) if klass.nil?
|
402
|
+
workflow = klass.new(id) if klass.respond_to?(:new)
|
403
|
+
return workflow
|
404
|
+
end
|
405
|
+
|
406
|
+
def read_flow_class(id)
|
407
|
+
klass = nil
|
408
|
+
raw_class = id.split('__').first
|
409
|
+
klass = begin
|
410
|
+
raw_class.constantize
|
411
|
+
rescue NameError => _
|
412
|
+
Rails.logger.warn("Unknown flow class for workflow id #{id}")
|
413
|
+
nil
|
414
|
+
end if !raw_class.nil?
|
415
|
+
|
416
|
+
return klass
|
417
|
+
end
|
418
|
+
|
419
|
+
def registered?(workflow)
|
420
|
+
return registry.include?(workflow)
|
421
|
+
end
|
422
|
+
|
423
|
+
def register(workflow)
|
424
|
+
registry.add(workflow)
|
425
|
+
end
|
426
|
+
|
427
|
+
def unregister(workflow)
|
428
|
+
registry.remove(workflow)
|
429
|
+
end
|
430
|
+
|
431
|
+
def terminal?(state)
|
432
|
+
return self::STATES_TERMINAL.include?(state)
|
433
|
+
end
|
434
|
+
|
435
|
+
def failure?(state)
|
436
|
+
return self::STATES_FAILED.include?(state)
|
437
|
+
end
|
438
|
+
|
439
|
+
def registry
|
440
|
+
return @registry ||= begin
|
441
|
+
FlowRegistry.new(Rworkflow::VERSION.to_s)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def serializer
|
446
|
+
YAML
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Rworkflow
|
2
|
+
class FlowRegistry
|
3
|
+
REDIS_PREFIX = 'flow:__registry'.freeze
|
4
|
+
|
5
|
+
def initialize(prefix = nil)
|
6
|
+
@redis_key = [REDIS_PREFIX, prefix].compact.join(':')
|
7
|
+
@public = RedisRds::SortedSet.new("#{@redis_key}:public")
|
8
|
+
@private = RedisRds::SortedSet.new("#{@redis_key}:private")
|
9
|
+
end
|
10
|
+
|
11
|
+
# Warning: using parent_class forces us to load everything, make this potentially much slower as we have to do the
|
12
|
+
# pagination in the app, not in the db
|
13
|
+
def all(options = {})
|
14
|
+
return self.public_flows(options) + self.private_flows(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def public_flows(options = {})
|
18
|
+
return get(@public, **options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def private_flows(options = {})
|
22
|
+
return get(@private, **options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(flow)
|
26
|
+
key = flow.created_at.to_i
|
27
|
+
|
28
|
+
if flow.public?
|
29
|
+
@public.add(key, flow.id)
|
30
|
+
else
|
31
|
+
@private.add(key, flow.id)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove(flow)
|
36
|
+
if flow.public?
|
37
|
+
@public.remove(flow.id)
|
38
|
+
else
|
39
|
+
@private.remove(flow.id)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def include?(flow)
|
44
|
+
if flow.public?
|
45
|
+
@public.include?(flow.id)
|
46
|
+
else
|
47
|
+
@private.include?(flow.id)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def get(zset, parent_class: nil, from: nil, to: nil, order: :asc)
|
52
|
+
from = from.to_i
|
53
|
+
to = to.nil? ? -1 : to.to_i
|
54
|
+
|
55
|
+
ids = []
|
56
|
+
if parent_class.nil? || parent_class == Flow
|
57
|
+
ids = zset.range(from, to, order: order)
|
58
|
+
else
|
59
|
+
ids = zset.range(0, -1, order: order).select do |id|
|
60
|
+
klass = Flow.read_flow_class(id)
|
61
|
+
!klass.nil? && klass <= parent_class
|
62
|
+
end.slice(from..to)
|
63
|
+
end
|
64
|
+
|
65
|
+
return ids
|
66
|
+
end
|
67
|
+
private :get
|
68
|
+
end
|
69
|
+
end
|