gush-mmx 0.4.2
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/.gitignore +21 -0
- data/.rspec +1 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +258 -0
- data/Rakefile +1 -0
- data/bin/gush +18 -0
- data/gush.gemspec +34 -0
- data/lib/gush.rb +61 -0
- data/lib/gush/cli.rb +160 -0
- data/lib/gush/cli/overview.rb +143 -0
- data/lib/gush/client.rb +239 -0
- data/lib/gush/configuration.rb +46 -0
- data/lib/gush/errors.rb +4 -0
- data/lib/gush/graph.rb +88 -0
- data/lib/gush/job.rb +128 -0
- data/lib/gush/json.rb +11 -0
- data/lib/gush/worker.rb +104 -0
- data/lib/gush/workflow.rb +206 -0
- data/spec/Gushfile.rb +0 -0
- data/spec/features/integration_spec.rb +122 -0
- data/spec/gush/cli_spec.rb +8 -0
- data/spec/gush/client_spec.rb +128 -0
- data/spec/gush/configuration_spec.rb +27 -0
- data/spec/gush/graph_spec.rb +43 -0
- data/spec/gush/job_spec.rb +113 -0
- data/spec/gush/json_spec.rb +21 -0
- data/spec/gush/worker_spec.rb +71 -0
- data/spec/gush/workflow_spec.rb +251 -0
- data/spec/gush_spec.rb +39 -0
- data/spec/spec_helper.rb +92 -0
- metadata +289 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 2f55fd6914dcc9cf58d63c7bf6df6489c8f9eb76
|
|
4
|
+
data.tar.gz: aa4bcedcb43924b69197c361e5c4964ac42e196d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 51223f508abf7a0e6286bc34f118553d49ecc7e9b49dc9c62e1a5adf7e83ae453a59efd02679d49bfa6d3b01a5bf41a068f0cc79224e5d60d9f1f9aea4677dd5
|
|
7
|
+
data.tar.gz: 200c6e51fe0d8bebc12d7ec8989bd7883392f5ea7e51463ef6acfe2df29df7440d925875980f2e676062d5feb625d313412ec00ef74d9f129a75f59f95d6085c
|
data/.gitignore
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
*.gem
|
|
2
|
+
*.rbc
|
|
3
|
+
.bundle
|
|
4
|
+
.config
|
|
5
|
+
.yardoc
|
|
6
|
+
Gemfile.lock
|
|
7
|
+
InstalledFiles
|
|
8
|
+
_yardoc
|
|
9
|
+
coverage
|
|
10
|
+
doc/
|
|
11
|
+
lib/bundler/man
|
|
12
|
+
pkg
|
|
13
|
+
rdoc
|
|
14
|
+
spec/reports
|
|
15
|
+
test/tmp
|
|
16
|
+
test/version_tmp
|
|
17
|
+
workflows/
|
|
18
|
+
tmp
|
|
19
|
+
test.rb
|
|
20
|
+
/Gushfile.rb
|
|
21
|
+
dump.rdb
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2015 Chaps sp. z o.o.
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Gush [](https://travis-ci.org/chaps-io/gush)
|
|
2
|
+
|
|
3
|
+
## [](https://chaps.io) proudly made by [Chaps](https://chaps.io)
|
|
4
|
+
|
|
5
|
+
Gush is a parallel workflow runner using only Redis as its message broker and Sidekiq for workers.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# New
|
|
9
|
+
|
|
10
|
+
Options for config:
|
|
11
|
+
* redis_prefix - Redis namespace
|
|
12
|
+
* sidekiq_queue - queue name in Sidekiq
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Theory
|
|
17
|
+
|
|
18
|
+
Gush relies on directed acyclic graphs to store dependencies, see [Parallelizing Operations With Dependencies](https://msdn.microsoft.com/en-us/magazine/dd569760.aspx) by Stephen Toub.
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Add this line to your application's Gemfile:
|
|
22
|
+
|
|
23
|
+
gem 'gush'
|
|
24
|
+
|
|
25
|
+
And then execute:
|
|
26
|
+
|
|
27
|
+
$ bundle
|
|
28
|
+
|
|
29
|
+
Or install it yourself as:
|
|
30
|
+
|
|
31
|
+
$ gem install gush
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Defining workflows
|
|
36
|
+
|
|
37
|
+
The DSL for defining jobs consists of a single `run` method.
|
|
38
|
+
Here is a complete example of a workflow you can create:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# workflows/sample_workflow.rb
|
|
42
|
+
class SampleWorkflow < Gush::Workflow
|
|
43
|
+
def configure(url_to_fetch_from)
|
|
44
|
+
run FetchJob1, params: { url: url_to_fetch_from }
|
|
45
|
+
run FetchJob2, params: {some_flag: true, url: 'http://url.com'}
|
|
46
|
+
|
|
47
|
+
run PersistJob1, after: FetchJob1
|
|
48
|
+
run PersistJob2, after: FetchJob2
|
|
49
|
+
|
|
50
|
+
run Normalize,
|
|
51
|
+
after: [PersistJob1, PersistJob2],
|
|
52
|
+
before: Index
|
|
53
|
+
|
|
54
|
+
run Index
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Hint:** For debugging purposes you can vizualize the graph using `viz` command:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
bundle exec gush viz SampleWorkflow
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For the Workflow above, the graph will look like this:
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
#### Passing parameters to jobs
|
|
71
|
+
|
|
72
|
+
You can pass any primitive arguments into jobs while defining your workflow:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
# app/workflows/sample_workflow.rb
|
|
76
|
+
class SampleWorkflow < Gush::Workflow
|
|
77
|
+
def configure
|
|
78
|
+
run FetchJob1, params: { url: "http://some.com/url" }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
See below to learn how to access those params inside your job.
|
|
84
|
+
|
|
85
|
+
#### Defining jobs
|
|
86
|
+
|
|
87
|
+
Jobs are classes inheriting from `Gush::Job`:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
# app/jobs/fetch_job.rb
|
|
91
|
+
class FetchJob < Gush::Job
|
|
92
|
+
def work
|
|
93
|
+
# do some fetching from remote APIs
|
|
94
|
+
|
|
95
|
+
params #=> {url: "http://some.com/url"}
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`params` method is a hash containing your (optional) parameters passed to `run` method in the workflow.
|
|
101
|
+
|
|
102
|
+
#### Passing arguments to workflows
|
|
103
|
+
|
|
104
|
+
Workflows can accept any primitive arguments in their constructor, which then will be available in your
|
|
105
|
+
`configure` method.
|
|
106
|
+
|
|
107
|
+
Here's an example of a workflow responsible for publishing a book:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# app/workflows/sample_workflow.rb
|
|
111
|
+
class PublishBookWorkflow < Gush::Workflow
|
|
112
|
+
def configure(url, isbn)
|
|
113
|
+
run FetchBook, params: { url: url }
|
|
114
|
+
run PublishBook, params: { book_isbn: isbn }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
and then create your workflow with those arguments:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
PublishBookWorkflow.new("http://url.com/book.pdf", "978-0470081204")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
### Running workflows
|
|
127
|
+
|
|
128
|
+
Now that we have defined our workflow we can use it:
|
|
129
|
+
|
|
130
|
+
#### 1. Initialize and save it
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
flow = SampleWorkflow.new(optional, arguments)
|
|
134
|
+
flow.save # saves workflow and its jobs to Redis
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**or:** you can also use a shortcut:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
flow = SampleWorkflow.create(optional, arguments)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### 2. Start workflow
|
|
144
|
+
|
|
145
|
+
First you need to start Sidekiq workers:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
bundle exec gush workers
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
and then start your workflow:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
flow.start!
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Now Gush will start processing jobs in background using Sidekiq
|
|
158
|
+
in the order defined in `configure` method inside Workflow.
|
|
159
|
+
|
|
160
|
+
### Pipelining
|
|
161
|
+
|
|
162
|
+
Gush offers a useful feature which lets you pass results of a job to its dependencies, so they can act accordingly.
|
|
163
|
+
|
|
164
|
+
**Example:**
|
|
165
|
+
|
|
166
|
+
Let's assume you have two jobs, `DownloadVideo`, `EncodeVideo`.
|
|
167
|
+
The latter needs to know where the first one downloaded the file to be able to open it.
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
class DownloadVideo < Gush::Job
|
|
172
|
+
def work
|
|
173
|
+
downloader = VideoDownloader.fetch("http://youtube.com/?v=someytvideo")
|
|
174
|
+
|
|
175
|
+
output(downloader.file_path)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`output` method is Gush's way of saying: "I want to pass this down to my descendants".
|
|
181
|
+
|
|
182
|
+
Now, since `DownloadVideo` finished and its dependant job `EncodeVideo` started, we can access that payload down the (pipe)line:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
class EncodeVideo < Gush::Job
|
|
186
|
+
def work
|
|
187
|
+
video_path = payloads["DownloadVideo"]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`payloads` is a hash containing outputs from all parent jobs, where job class names are the keys.
|
|
193
|
+
|
|
194
|
+
**Note:** `payloads` will only contain outputs of the job's ancestors. So if job `A` depends on `B` and `C`,
|
|
195
|
+
the `payloads` hash will look like this:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
{
|
|
199
|
+
"B" => (...),
|
|
200
|
+
"C" => (...)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
### Checking status:
|
|
206
|
+
|
|
207
|
+
#### In Ruby:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
flow.reload
|
|
211
|
+
flow.status
|
|
212
|
+
#=> :running|:finished|:failed
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`reload` is needed to see the latest status, since workflows are updated asynchronously.
|
|
216
|
+
|
|
217
|
+
#### Via CLI:
|
|
218
|
+
|
|
219
|
+
- of a specific workflow:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
bundle exec gush show <workflow_id>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- of all created workflows:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
bundle exec gush list
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
### Requiring workflows inside your projects
|
|
233
|
+
|
|
234
|
+
When using Gush and its CLI commands you need a Gushfile.rb in root directory.
|
|
235
|
+
Gushfile should require all your Workflows and jobs, for example:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
require_relative './lib/your_project'
|
|
239
|
+
|
|
240
|
+
Dir[Rails.root.join("app/workflows/**/*.rb")].each do |file|
|
|
241
|
+
require file
|
|
242
|
+
end
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Contributors
|
|
246
|
+
|
|
247
|
+
- [Mateusz Lenik](https://github.com/mlen)
|
|
248
|
+
- [Michał Krzyżanowski](https://github.com/krzyzak)
|
|
249
|
+
- [Maciej Nowak](https://github.com/keqi)
|
|
250
|
+
- [Maciej Kołek](https://github.com/ferusinfo)
|
|
251
|
+
|
|
252
|
+
## Contributing
|
|
253
|
+
|
|
254
|
+
1. Fork it ( http://github.com/chaps-io/gush/fork )
|
|
255
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
256
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
257
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
258
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/gush
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require "pathname"
|
|
3
|
+
require "bundler"
|
|
4
|
+
Bundler.require
|
|
5
|
+
|
|
6
|
+
bin_file = Pathname.new(__FILE__).realpath
|
|
7
|
+
# add self to libpath
|
|
8
|
+
$:.unshift File.expand_path("../../lib", bin_file)
|
|
9
|
+
|
|
10
|
+
require 'gush'
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
Gush::CLI.start(ARGV)
|
|
14
|
+
rescue Gush::WorkflowNotFound
|
|
15
|
+
puts "Workflow not found".red
|
|
16
|
+
rescue Gush::DependencyLevelTooDeep
|
|
17
|
+
puts "Dependency level too deep. Perhaps you have a dependency cycle?".red
|
|
18
|
+
end
|
data/gush.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "gush-mmx"
|
|
7
|
+
spec.version = "0.4.2"
|
|
8
|
+
spec.authors = ["Piotrek Okoński", "Max Ivak"]
|
|
9
|
+
spec.email = ["piotrek@okonski.org", "maxivak@gmail.com"]
|
|
10
|
+
spec.summary = "Fast and distributed workflow runner using only Sidekiq and Redis"
|
|
11
|
+
spec.description = "Gush is a parallel workflow runner using only Redis as its message broker and Sidekiq for workers."
|
|
12
|
+
spec.homepage = "https://github.com/pokonski/gush"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
16
|
+
spec.executables = "gush"
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
spec.add_dependency "sidekiq", "~> 4.0"
|
|
21
|
+
spec.add_dependency "multi_json", "~> 1.11"
|
|
22
|
+
spec.add_dependency "redis", "~> 3.2"
|
|
23
|
+
spec.add_dependency "hiredis", "~> 0.6"
|
|
24
|
+
spec.add_dependency "ruby-graphviz", "~> 1.2"
|
|
25
|
+
spec.add_dependency "terminal-table", "~> 1.4"
|
|
26
|
+
spec.add_dependency "colorize", "~> 0.7"
|
|
27
|
+
spec.add_dependency "thor", "~> 0.19"
|
|
28
|
+
spec.add_dependency "launchy", "~> 2.4"
|
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
|
30
|
+
spec.add_development_dependency "rake", "~> 10.4"
|
|
31
|
+
spec.add_development_dependency "rspec", '~> 3.0'
|
|
32
|
+
spec.add_development_dependency "pry", '~> 0.10'
|
|
33
|
+
spec.add_development_dependency 'fakeredis', '~> 0.5'
|
|
34
|
+
end
|
data/lib/gush.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require "bundler/setup"
|
|
2
|
+
|
|
3
|
+
require "graphviz"
|
|
4
|
+
require "hiredis"
|
|
5
|
+
require "pathname"
|
|
6
|
+
require "redis"
|
|
7
|
+
require "securerandom"
|
|
8
|
+
require "sidekiq"
|
|
9
|
+
require "multi_json"
|
|
10
|
+
|
|
11
|
+
require "gush/json"
|
|
12
|
+
require "gush/cli"
|
|
13
|
+
require "gush/cli/overview"
|
|
14
|
+
require "gush/graph"
|
|
15
|
+
require "gush/client"
|
|
16
|
+
require "gush/configuration"
|
|
17
|
+
require "gush/errors"
|
|
18
|
+
require "gush/job"
|
|
19
|
+
require "gush/worker"
|
|
20
|
+
require "gush/workflow"
|
|
21
|
+
|
|
22
|
+
module Gush
|
|
23
|
+
def self.gushfile
|
|
24
|
+
configuration.gushfile
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.root
|
|
28
|
+
Pathname.new(__FILE__).parent.parent
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.configuration
|
|
32
|
+
@configuration ||= Configuration.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.configure
|
|
36
|
+
yield configuration
|
|
37
|
+
reconfigure_sidekiq
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.reconfigure_sidekiq
|
|
41
|
+
#puts "^^^^^^^^ reconfigure_sidekiq ^^^"
|
|
42
|
+
Sidekiq.configure_server do |config|
|
|
43
|
+
#config.redis = { url: configuration.redis_url, queue: configuration.namespace}
|
|
44
|
+
|
|
45
|
+
opts = { url: configuration.redis_url, namespace: configuration.redis_prefix, queue: configuration.sidekiq_queue }
|
|
46
|
+
#puts "sidekiq server opts: #{opts}"
|
|
47
|
+
config.redis = { url: configuration.redis_url, namespace: configuration.redis_prefix, queue: configuration.sidekiq_queue }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Sidekiq.configure_client do |config|
|
|
51
|
+
#config.redis = { url: configuration.redis_url, queue: configuration.namespace}
|
|
52
|
+
|
|
53
|
+
opts = { url: configuration.redis_url, namespace: configuration.redis_prefix, queue: configuration.sidekiq_queue }
|
|
54
|
+
#puts "sidekiq client opts: #{opts}"
|
|
55
|
+
|
|
56
|
+
config.redis = { url: configuration.redis_url, namespace: configuration.redis_prefix, queue: configuration.sidekiq_queue }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
#Gush.reconfigure_sidekiq
|