resque-heroku-autoscaler 0.1.0 → 0.2.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.
data/README.md
CHANGED
@@ -1,28 +1,59 @@
|
|
1
|
-
Resque Heroku Autoscaler
|
2
|
-
|
1
|
+
Resque Heroku Autoscaler (RHA)
|
2
|
+
=======================
|
3
3
|
|
4
|
-
A [Resque][rq] plugin. Requires Resque 1.8 and the Heroku gem (
|
4
|
+
A [Resque][rq] plugin. Requires Resque 1.8 and the Heroku gem (only tested with 1.11.0).
|
5
5
|
|
6
6
|
This gem scales your Heroku workers according to the number of pending Resque jobs. The original idea comes from Daniel Huckstep's [blog post on the topic][dh]
|
7
7
|
|
8
|
-
|
8
|
+
##Setup
|
9
9
|
|
10
|
+
In order for the scaling to work RHA needs to know your **Heroku app's name, your Heroku user name and your Heroku password**. Per default those are being read from the following environment variables:
|
11
|
+
|
12
|
+
- HEROKU_APP
|
13
|
+
- HEROKU_USER
|
14
|
+
- HEROKU_PASS
|
15
|
+
|
16
|
+
If you prefer you also can configure RHA using a config block. For example you might put the following into config/initialiazers/resque_heroku_autoscaler_setup.config
|
17
|
+
|
18
|
+
require 'resque/plugins/resque_heroku_autoscaler'
|
19
|
+
|
20
|
+
Resque::Plugins::HerokuAutoscaler.config do |c|
|
21
|
+
c.heroku_user = 'john doe'
|
22
|
+
c.heroku_pass = ENV['HEROKU_PASSWORD']
|
23
|
+
c.heroku_app = "my_app_#{Rails.env}"
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
To use RHA in one of your jobs, just extend your job class with Resque::Plugins::HerokuAutoscaler.
|
10
28
|
|
11
29
|
For example:
|
12
30
|
|
13
|
-
require 'resque/plugins/
|
31
|
+
require 'resque/plugins/resque_heroku_autoscaler'
|
14
32
|
|
15
33
|
class TestJob
|
16
34
|
extend Resque::Plugins::HerokuAutoscaler
|
17
35
|
|
18
36
|
@queue = :test
|
19
37
|
|
20
|
-
def
|
38
|
+
def perform
|
39
|
+
...awesome stuff...
|
40
|
+
end
|
21
41
|
end
|
22
42
|
|
23
43
|
When you add the job to your Resque queue, a new worker will be started if there isn't already one. If all jobs in the queue are processed the worker will be stopped again, keeping your costs low.
|
24
44
|
|
25
|
-
|
45
|
+
Per default RHA will only start a single worker, no matter how many jobs are pending. You can change this behavior in the config block as well:
|
46
|
+
|
47
|
+
require 'resque/plugins/resque_heroku_autoscaler'
|
48
|
+
|
49
|
+
Resque::Plugins::HerokuAutoscaler.config do |c|
|
50
|
+
c.new_worker_count do |pending|
|
51
|
+
(pending/5).ceil.to_i
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
When calculating the new number of required workers the block given to new_worker_count will be called. Thus the example will result in starting one additional worker for every 5 pending jobs.
|
56
|
+
|
26
57
|
|
27
58
|
[dh]: http://blog.darkhax.com/2010/07/30/auto-scale-your-resque-workers-on-heroku
|
28
59
|
[rq]: http://github.com/defunkt/resque
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Resque
|
2
|
+
module Plugins
|
3
|
+
module HerokuAutoscaler
|
4
|
+
module Config
|
5
|
+
extend self
|
6
|
+
|
7
|
+
@new_worker_count = Proc.new {|pending| pending >0 ? 1 : 0}
|
8
|
+
|
9
|
+
attr_writer :heroku_user
|
10
|
+
def heroku_user
|
11
|
+
@heroku_user || ENV['HEROKU_USER']
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_writer :heroku_pass
|
15
|
+
def heroku_pass
|
16
|
+
@heroku_pass || ENV['HEROKU_PASS']
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_writer :heroku_app
|
20
|
+
def heroku_app
|
21
|
+
@heroku_app || ENV['HEROKU_APP']
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_worker_count(pending=nil, *payload, &calculate_count)
|
25
|
+
if calculate_count
|
26
|
+
@new_worker_count = calculate_count
|
27
|
+
else
|
28
|
+
@new_worker_count.call(pending, *payload)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,22 +1,35 @@
|
|
1
|
+
require 'resque/plugins/heroku_autoscaler/config'
|
2
|
+
|
1
3
|
module Resque
|
2
4
|
module Plugins
|
3
5
|
module HerokuAutoscaler
|
4
6
|
@@heroku_client = nil
|
5
7
|
|
6
8
|
def after_enqueue_scale_workers_up(*args)
|
7
|
-
set_workers(
|
9
|
+
set_workers(Resque::Plugins::HerokuAutoscaler::Config.new_worker_count(Resque.info[:pending]))
|
8
10
|
end
|
9
11
|
|
10
12
|
def after_perform_scale_workers_down(*args)
|
11
|
-
set_workers(
|
13
|
+
set_workers(Resque::Plugins::HerokuAutoscaler::Config.new_worker_count(Resque.info[:pending]))
|
12
14
|
end
|
13
15
|
|
14
16
|
def set_workers(number_of_workers)
|
15
|
-
|
17
|
+
if number_of_workers != current_workers
|
18
|
+
heroku_client.set_workers(Resque::Plugins::HerokuAutoscaler::Config.heroku_app, number_of_workers)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_workers
|
23
|
+
heroku_client.info(Resque::Plugins::HerokuAutoscaler::Config.heroku_app)[:workers].to_i
|
16
24
|
end
|
17
25
|
|
18
26
|
def heroku_client
|
19
|
-
@@heroku_client || @@heroku_client = Heroku::Client.new(
|
27
|
+
@@heroku_client || @@heroku_client = Heroku::Client.new(Resque::Plugins::HerokuAutoscaler::Config.heroku_user,
|
28
|
+
Resque::Plugins::HerokuAutoscaler::Config.heroku_pass)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.config
|
32
|
+
yield Resque::Plugins::HerokuAutoscaler::Config
|
20
33
|
end
|
21
34
|
end
|
22
35
|
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'heroku'
|
3
|
+
require 'resque'
|
4
|
+
require 'resque/plugins/heroku_autoscaler/config'
|
5
|
+
|
6
|
+
describe Resque::Plugins::HerokuAutoscaler::Config do
|
7
|
+
describe ".heroku_user" do
|
8
|
+
it "stores the given heroku user name" do
|
9
|
+
subject.heroku_user = "my_user@example.com"
|
10
|
+
subject.heroku_user.should == "my_user@example.com"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "defaults to HEROKU_USER environment variable" do
|
14
|
+
subject.heroku_user = nil
|
15
|
+
ENV["HEROKU_USER"] = "user@example.com"
|
16
|
+
subject.heroku_user.should == "user@example.com"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ".heroku_pass" do
|
21
|
+
it "stores the given heroku password" do
|
22
|
+
subject.heroku_pass = "password"
|
23
|
+
subject.heroku_pass.should == "password"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "defaults to HEROKU_PASS environment variable" do
|
27
|
+
subject.heroku_pass = nil
|
28
|
+
ENV["HEROKU_PASS"] = "123"
|
29
|
+
subject.heroku_pass.should == "123"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ".heroku_app" do
|
34
|
+
it "stores the given heroku application name" do
|
35
|
+
subject.heroku_app = "my-grand-app"
|
36
|
+
subject.heroku_app.should == "my-grand-app"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "defaults to HEROKU_APP environment variable" do
|
40
|
+
subject.heroku_app = nil
|
41
|
+
ENV["HEROKU_APP"] = "yaa"
|
42
|
+
subject.heroku_app.should == "yaa"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe ".new_worker_count" do
|
47
|
+
before do
|
48
|
+
@original_method = Resque::Plugins::HerokuAutoscaler::Config.instance_variable_get(:@new_worker_count)
|
49
|
+
end
|
50
|
+
|
51
|
+
after do
|
52
|
+
Resque::Plugins::HerokuAutoscaler::Config.instance_variable_set(:@new_worker_count, @original_method)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should store a block as a Proc" do
|
56
|
+
subject.new_worker_count do |pending|
|
57
|
+
pending/5
|
58
|
+
end
|
59
|
+
|
60
|
+
subject.new_worker_count(10).should == 2
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should be able to take the Resque job's payload as arguments" do
|
64
|
+
subject.new_worker_count do |pending, queue|
|
65
|
+
if queue == "test_queue"
|
66
|
+
10
|
67
|
+
else
|
68
|
+
pending/5
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
job_payload = ["test_queue", "more", "payload"]
|
73
|
+
subject.new_worker_count(10, *job_payload).should == 10
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when the proc was not yet set" do
|
77
|
+
before do
|
78
|
+
subject.new_worker_count do |pending, queue|
|
79
|
+
end
|
80
|
+
it { subject.new_worker_count(0).should == 0 }
|
81
|
+
it { subject.new_worker_count(1).should == 1 }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -20,6 +20,12 @@ RSpec.configure do |config|
|
|
20
20
|
end
|
21
21
|
|
22
22
|
describe Resque::Plugins::HerokuAutoscaler do
|
23
|
+
before do
|
24
|
+
@fake_heroku_client = Object.new
|
25
|
+
stub(@fake_heroku_client).set_workers
|
26
|
+
stub(@fake_heroku_client).info { {:workers => 0} }
|
27
|
+
end
|
28
|
+
|
23
29
|
it "should be a valid Resque plugin" do
|
24
30
|
lambda { Resque::Plugin.lint(Resque::Plugins::HerokuAutoscaler) }.should_not raise_error
|
25
31
|
end
|
@@ -30,25 +36,51 @@ describe Resque::Plugins::HerokuAutoscaler do
|
|
30
36
|
end
|
31
37
|
|
32
38
|
it "should take whatever args Resque hands in" do
|
33
|
-
stub(
|
39
|
+
stub(TestJob).heroku_client { @fake_heroku_client }
|
34
40
|
|
35
|
-
lambda
|
41
|
+
lambda do
|
42
|
+
TestJob.after_enqueue_scale_workers_up("some", "random", "aguments", 42)
|
43
|
+
end.should_not raise_error
|
36
44
|
end
|
37
45
|
|
38
46
|
it "should create one worker" do
|
39
47
|
stub(TestJob).workers { 0 }
|
48
|
+
stub(Resque).info{ {:pending => 1} }
|
40
49
|
mock(TestJob).set_workers(1)
|
41
50
|
TestJob.after_enqueue_scale_workers_up
|
42
51
|
end
|
52
|
+
|
53
|
+
context "when new_worker_count was changed" do
|
54
|
+
before do
|
55
|
+
@original_method = Resque::Plugins::HerokuAutoscaler::Config.instance_variable_get(:@new_worker_count)
|
56
|
+
subject.config do |c|
|
57
|
+
c.new_worker_count do
|
58
|
+
2
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
after do
|
64
|
+
Resque::Plugins::HerokuAutoscaler::Config.instance_variable_set(:@new_worker_count, @original_method)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should use the given block" do
|
68
|
+
mock(TestJob).set_workers(2)
|
69
|
+
TestJob.after_enqueue_scale_workers_up
|
70
|
+
end
|
71
|
+
end
|
43
72
|
end
|
44
73
|
|
45
74
|
describe ".after_perform_scale_workers_down" do
|
75
|
+
before do
|
76
|
+
stub(TestJob).heroku_client { @fake_heroku_client }
|
77
|
+
end
|
46
78
|
|
47
79
|
it "should add the hook" do
|
48
80
|
Resque::Plugin.after_hooks(TestJob).should include("after_perform_scale_workers_down")
|
49
81
|
end
|
50
82
|
|
51
|
-
it "should take whatever args Resque hands in" do
|
83
|
+
it "should take whatever args Resque hands in" do
|
52
84
|
Resque::Plugins::HerokuAutoscaler.class_eval("@@heroku_client = nil")
|
53
85
|
stub(Heroku::Client).new { stub!.set_workers }
|
54
86
|
|
@@ -71,8 +103,28 @@ describe Resque::Plugins::HerokuAutoscaler do
|
|
71
103
|
stub(Resque).info { {:pending => 1} }
|
72
104
|
end
|
73
105
|
|
74
|
-
it "should
|
75
|
-
|
106
|
+
it "should keep workers at 1" do
|
107
|
+
mock(TestJob).set_workers(1)
|
108
|
+
TestJob.after_perform_scale_workers_down
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "when new_worker_count was changed" do
|
113
|
+
before do
|
114
|
+
@original_method = Resque::Plugins::HerokuAutoscaler::Config.instance_variable_get(:@new_worker_count)
|
115
|
+
subject.config do |c|
|
116
|
+
c.new_worker_count do
|
117
|
+
2
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
after do
|
123
|
+
Resque::Plugins::HerokuAutoscaler::Config.instance_variable_set(:@new_worker_count, @original_method)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should use the given block" do
|
127
|
+
mock(TestJob).set_workers(2)
|
76
128
|
TestJob.after_perform_scale_workers_down
|
77
129
|
end
|
78
130
|
end
|
@@ -80,16 +132,22 @@ describe Resque::Plugins::HerokuAutoscaler do
|
|
80
132
|
|
81
133
|
describe ".set_workers" do
|
82
134
|
it "should use the Heroku client to set the workers" do
|
83
|
-
|
84
|
-
|
135
|
+
subject.config do |c|
|
136
|
+
c.heroku_app = 'some_app_name'
|
137
|
+
end
|
138
|
+
|
139
|
+
stub(TestJob).current_workers {0}
|
140
|
+
mock(TestJob).heroku_client { p "returning"; mock(@fake_heroku_client).set_workers('some_app_name', 10) }
|
85
141
|
TestJob.set_workers(10)
|
86
142
|
end
|
87
143
|
end
|
88
144
|
|
89
145
|
describe ".heroku_client" do
|
90
146
|
before do
|
91
|
-
|
92
|
-
|
147
|
+
subject.config do |c|
|
148
|
+
c.heroku_user = 'john doe'
|
149
|
+
c.heroku_pass = 'password'
|
150
|
+
end
|
93
151
|
end
|
94
152
|
|
95
153
|
it "should return a heroku client" do
|
@@ -116,4 +174,23 @@ describe Resque::Plugins::HerokuAutoscaler do
|
|
116
174
|
TestJob.heroku_client.should == AnotherJob.heroku_client
|
117
175
|
end
|
118
176
|
end
|
119
|
-
|
177
|
+
|
178
|
+
describe ".config" do
|
179
|
+
it "yields the configuration" do
|
180
|
+
subject.config do |c|
|
181
|
+
c.should == Resque::Plugins::HerokuAutoscaler::Config
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe ".current_workers" do
|
187
|
+
it "should request the numbers of active workers from Heroku" do
|
188
|
+
subject.config do |c|
|
189
|
+
c.heroku_app = "my_app"
|
190
|
+
end
|
191
|
+
|
192
|
+
mock(TestJob).heroku_client { mock!.info("my_app") { {:workers => 10} } }
|
193
|
+
TestJob.current_workers.should == 10
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque-heroku-autoscaler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 2
|
8
9
|
- 1
|
9
|
-
|
10
|
-
version: 0.1.0
|
10
|
+
version: 0.2.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alexander Murmann
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-02-
|
18
|
+
date: 2011-02-11 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -47,7 +47,9 @@ dependencies:
|
|
47
47
|
version: "0"
|
48
48
|
type: :runtime
|
49
49
|
version_requirements: *id002
|
50
|
-
description:
|
50
|
+
description: |
|
51
|
+
This gem scales your Heroku workers according to the number of pending Resque jobs. You can customize the scaling behavior of your workers, however you like.
|
52
|
+
|
51
53
|
email: ajmurmann@gmail.com
|
52
54
|
executables: []
|
53
55
|
|
@@ -59,7 +61,10 @@ files:
|
|
59
61
|
- README.md
|
60
62
|
- MIT.LICENSE
|
61
63
|
- GEMFILE
|
64
|
+
- lib/resque/plugins/heroku_autoscaler/config.rb
|
65
|
+
- lib/resque/plugins/heroku_autoscaler/version.rb
|
62
66
|
- lib/resque/plugins/resque_heroku_autoscaler.rb
|
67
|
+
- spec/config_spec.rb
|
63
68
|
- spec/resque_heroku_autoscaler_spec.rb
|
64
69
|
has_rdoc: true
|
65
70
|
homepage: https://github.com/ajmurmann/resque-heroku-autoscaler
|