resque-heroku-autoscaler 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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 (I only testet with 1.11.0).
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
- Just extend your job class with esque::Plugins::HerokuAutoscaler.
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/heroku_autoscaler'
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
- Currently there is no way build in to set how many workers to are being started. I plan on adding that functionality soon.
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
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ module Plugins
3
+ module HerokuAutoscaler
4
+ VERSION = "0.2.1"
5
+ end
6
+ end
7
+ 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(1)
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(0) if Resque.info[:pending] == 0
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
- heroku_client.set_workers(ENV['HEROKU_APP'], number_of_workers)
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(ENV['HEROKU_USER'], ENV['HEROKU_PASS'])
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
@@ -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(Heroku::Client).new { stub!.set_workers }
39
+ stub(TestJob).heroku_client { @fake_heroku_client }
34
40
 
35
- lambda { TestJob.after_enqueue_scale_workers_up("some", "random", "aguments", 42) }.should_not raise_error
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 not change workers" do
75
- dont_allow(TestJob).set_workers
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
- ENV['HEROKU_APP'] = 'some app name'
84
- mock(TestJob).heroku_client { mock!.set_workers('some app name', 10) }
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
- ENV['HEROKU_USER'] = 'john doe'
92
- ENV['HEROKU_PASS'] = 'password'
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
- end
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: 27
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 2
8
9
  - 1
9
- - 0
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-06 00:00:00 -08:00
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