resque-heroku-scaler 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.md +6 -0
- data/LICENSE +20 -0
- data/README.md +52 -0
- data/Rakefile +9 -0
- data/lib/resque/plugins/resque-heroku-scaler.rb +6 -0
- data/lib/resque/plugins/resque_heroku_scaler/config.rb +71 -0
- data/lib/resque/plugins/resque_heroku_scaler/manager/heroku.rb +25 -0
- data/lib/resque/plugins/resque_heroku_scaler/manager/local.rb +56 -0
- data/lib/resque/plugins/resque_heroku_scaler/manager.rb +27 -0
- data/lib/resque/plugins/resque_heroku_scaler/resque.rb +14 -0
- data/lib/resque/plugins/resque_heroku_scaler/tasks.rb +11 -0
- data/lib/resque/plugins/resque_heroku_scaler/version.rb +7 -0
- data/lib/resque/plugins/resque_heroku_scaler/worker.rb +85 -0
- data/lib/resque/plugins/resque_heroku_scaler.rb +106 -0
- data/test/config_test.rb +61 -0
- data/test/resque_heroku_scaler_test.rb +68 -0
- data/test/test_helper.rb +4 -0
- data/test/worker_test.rb +26 -0
- metadata +94 -0
data/HISTORY.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) Aaron Dunnington
|
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/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
Resque Heroku Scaler
|
2
|
+
====================
|
3
|
+
|
4
|
+
Provides autoscaling for [Resque][rq] workers on [Heroku][hk]. Based on
|
5
|
+
previous scaling work developed by [Daniel Huckstep][dh] and
|
6
|
+
[Alexander Murmann][am].
|
7
|
+
|
8
|
+
Autoscaling behavior is provided through a separate monitor process. The
|
9
|
+
scaler monitor process polls for pending jobs against the specified Resque
|
10
|
+
Redis backend at a configurable interval. The scaler process runs as a worker
|
11
|
+
process on Heroku.
|
12
|
+
|
13
|
+
##Setup
|
14
|
+
|
15
|
+
Add the following environment variables to your Heroku environment:
|
16
|
+
|
17
|
+
* HEROKU_APP
|
18
|
+
* HEROKU_USERNAME
|
19
|
+
* HEROKU_PASSWORD
|
20
|
+
|
21
|
+
In your Procfile, configure the scaler as a worker process using:
|
22
|
+
|
23
|
+
```
|
24
|
+
scaler: bundle exec rake resque:scaler:run
|
25
|
+
```
|
26
|
+
|
27
|
+
To run the scaler process, use the following command. Note, the scaler process
|
28
|
+
is intended to run as a single instance.
|
29
|
+
|
30
|
+
```
|
31
|
+
heroku scale scaler=1
|
32
|
+
```
|
33
|
+
|
34
|
+
In your development environment, the scaler process can run local worker
|
35
|
+
processes using the rush library. To configure, use the following in
|
36
|
+
an initializer.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require 'resque/plugins/resque-heroku-scaler'
|
40
|
+
|
41
|
+
if Rails.env.development?
|
42
|
+
ENV["RUSH_PATH"] ||= File.expand_path('/path/to/app', __FILE__)
|
43
|
+
Resque::Plugins::ResqueHerokuScaler.configure do |c|
|
44
|
+
c.scale_manager = :local
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
[rq]: http://github.com/defunkt/resque
|
50
|
+
[hk]: http://devcenter.heroku.com/articles/cedar
|
51
|
+
[dh]: http://verboselogging.com/2010/07/30/auto-scale-your-resque-workers-on-heroku
|
52
|
+
[am]: https://github.com/ajmurmann/resque-heroku-autoscaler
|
data/Rakefile
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
require 'resque/plugins/resque_heroku_scaler/version'
|
2
|
+
require 'resque/plugins/resque_heroku_scaler/config'
|
3
|
+
require 'resque/plugins/resque_heroku_scaler/manager'
|
4
|
+
require 'resque/plugins/resque_heroku_scaler/worker'
|
5
|
+
require 'resque/plugins/resque_heroku_scaler/resque'
|
6
|
+
require 'resque/plugins/resque_heroku_scaler'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Resque
|
2
|
+
module Plugins
|
3
|
+
module ResqueHerokuScaler
|
4
|
+
module Config
|
5
|
+
extend self
|
6
|
+
|
7
|
+
attr_writer :scale_manager
|
8
|
+
attr_writer :scale_interval
|
9
|
+
attr_writer :poll_interval
|
10
|
+
attr_writer :scale_timeout
|
11
|
+
attr_reader :scale_with
|
12
|
+
|
13
|
+
def scale_manager
|
14
|
+
@scale_manager || :heroku
|
15
|
+
end
|
16
|
+
|
17
|
+
def scale_interval
|
18
|
+
@scale_interval || 60
|
19
|
+
end
|
20
|
+
|
21
|
+
def poll_interval
|
22
|
+
@poll_interval || 5
|
23
|
+
end
|
24
|
+
|
25
|
+
def scale_timeout
|
26
|
+
@scale_timeout || 90
|
27
|
+
end
|
28
|
+
|
29
|
+
def scale_for(pending)
|
30
|
+
return @scale_with.call(pending) if @scale_with
|
31
|
+
default_scale_with(pending)
|
32
|
+
end
|
33
|
+
|
34
|
+
def scale_with=(block)
|
35
|
+
@scale_with = block
|
36
|
+
end
|
37
|
+
|
38
|
+
def default_scale_with(pending)
|
39
|
+
return 0 if pending <= 0
|
40
|
+
|
41
|
+
[{
|
42
|
+
:workers => 1,
|
43
|
+
:jobs => 1
|
44
|
+
},
|
45
|
+
{
|
46
|
+
:workers => 2,
|
47
|
+
:jobs => 15
|
48
|
+
},
|
49
|
+
{
|
50
|
+
:workers => 3,
|
51
|
+
:jobs => 25
|
52
|
+
},
|
53
|
+
{
|
54
|
+
:workers => 4,
|
55
|
+
:jobs => 40
|
56
|
+
},
|
57
|
+
{
|
58
|
+
:workers => 5,
|
59
|
+
:jobs => 60
|
60
|
+
}
|
61
|
+
].reverse_each do |required_scale|
|
62
|
+
if pending >= required_scale[:jobs]
|
63
|
+
return required_scale[:workers]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
return 0
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'heroku'
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
module Plugins
|
5
|
+
module ResqueHerokuScaler
|
6
|
+
module Manager
|
7
|
+
|
8
|
+
class Heroku
|
9
|
+
def initialize(options={})
|
10
|
+
@heroku = ::Heroku::Client.new(ENV['HEROKU_USERNAME'], ENV['HEROKU_PASSWORD'])
|
11
|
+
end
|
12
|
+
|
13
|
+
def workers
|
14
|
+
@heroku.ps(ENV['HEROKU_APP']).count { |p| p["process"] =~ /worker\.\d?/ }
|
15
|
+
end
|
16
|
+
|
17
|
+
def workers=(qty)
|
18
|
+
@heroku.ps_scale(ENV['HEROKU_APP'], :type => 'worker', :qty => qty)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rush'
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
module Plugins
|
5
|
+
module ResqueHerokuScaler
|
6
|
+
module Manager
|
7
|
+
|
8
|
+
class Local
|
9
|
+
|
10
|
+
def initialize(options={})
|
11
|
+
@path = options[:path] || ENV['RUSH_PATH']
|
12
|
+
@processes = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def workers
|
16
|
+
@processes.length
|
17
|
+
end
|
18
|
+
|
19
|
+
def workers=(qty)
|
20
|
+
active = workers
|
21
|
+
return if qty == active
|
22
|
+
if qty > active
|
23
|
+
scale_up(qty-active)
|
24
|
+
return
|
25
|
+
end
|
26
|
+
scale_down(active-qty)
|
27
|
+
end
|
28
|
+
|
29
|
+
def scale_up(qty)
|
30
|
+
qty.times do
|
31
|
+
process = Rush::Box.new[@path].bash('rake resque:work', :background => true, :env => { :BUNDLE_GEMFILE => '' })
|
32
|
+
@processes.push(process) if process
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def scale_down(qty)
|
37
|
+
i = 0
|
38
|
+
until i == qty or @processes.empty?
|
39
|
+
process = @processes.pop
|
40
|
+
kill(process)
|
41
|
+
i += 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def kill(process)
|
46
|
+
process.children.each do |child|
|
47
|
+
kill(child)
|
48
|
+
end
|
49
|
+
process.kill
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Resque
|
2
|
+
module Plugins
|
3
|
+
module ResqueHerokuScaler
|
4
|
+
module Manager
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def instance
|
8
|
+
@@instance ||= init_manager
|
9
|
+
end
|
10
|
+
|
11
|
+
def init_manager
|
12
|
+
handler = Resque::Plugins::ResqueHerokuScaler::Config.scale_manager
|
13
|
+
return handler unless [Symbol, Array, String].include? handler.class
|
14
|
+
|
15
|
+
options = {}
|
16
|
+
handler, options = handler if handler.is_a?(Array)
|
17
|
+
require File.dirname(__FILE__) + "/manager/#{handler}"
|
18
|
+
const_get(handler.to_s.capitalize).new(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(m, *args)
|
22
|
+
instance.send(m, *args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Resque
|
2
|
+
|
3
|
+
class Worker
|
4
|
+
alias_method :original_unregister_worker, :unregister_worker
|
5
|
+
|
6
|
+
def work(interval = 5.0, &block)
|
7
|
+
interval = Float(interval)
|
8
|
+
$0 = "resque: Starting"
|
9
|
+
startup
|
10
|
+
|
11
|
+
loop do
|
12
|
+
wait_for_scale if scaling?
|
13
|
+
break if shutdown?
|
14
|
+
|
15
|
+
if not paused? and job = reserve
|
16
|
+
log "got: #{job.inspect}"
|
17
|
+
job.worker = self
|
18
|
+
run_hook :before_fork, job
|
19
|
+
working_on job
|
20
|
+
|
21
|
+
if @child = fork
|
22
|
+
srand # Reseeding
|
23
|
+
procline "Forked #{@child} at #{Time.now.to_i}"
|
24
|
+
Process.wait(@child)
|
25
|
+
else
|
26
|
+
procline "Processing #{job.queue} since #{Time.now.to_i}"
|
27
|
+
perform(job, &block)
|
28
|
+
exit! unless @cant_fork
|
29
|
+
end
|
30
|
+
|
31
|
+
done_working
|
32
|
+
@child = nil
|
33
|
+
else
|
34
|
+
break if interval.zero?
|
35
|
+
log! "Sleeping for #{interval} seconds"
|
36
|
+
procline paused? ? "Paused" : "Waiting for #{@queues.join(',')}"
|
37
|
+
sleep interval
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
ensure
|
42
|
+
unregister_worker
|
43
|
+
end
|
44
|
+
|
45
|
+
def wait_for_scale
|
46
|
+
redis.set("scale:#{self}", true)
|
47
|
+
sleep 1 while scaling? and not shutdown?
|
48
|
+
redis.del("scale:#{self}")
|
49
|
+
end
|
50
|
+
|
51
|
+
def unregister_worker
|
52
|
+
redis.del("scale:#{self}")
|
53
|
+
original_unregister_worker
|
54
|
+
end
|
55
|
+
|
56
|
+
def scaling?
|
57
|
+
redis.exists(:scale)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.scaling
|
61
|
+
names = all
|
62
|
+
return [] unless names.any?
|
63
|
+
|
64
|
+
names.map! { |name| "scale:#{name}" }
|
65
|
+
|
66
|
+
reportedly_scaling = {}
|
67
|
+
|
68
|
+
begin
|
69
|
+
reportedly_scaling = redis.mapped_mget(*names).reject do |key, value|
|
70
|
+
value.nil? || value.empty?
|
71
|
+
end
|
72
|
+
rescue Redis::Distributed::CannotDistribute
|
73
|
+
names.each do |name|
|
74
|
+
value = redis.get name
|
75
|
+
reportedly_scaling[name] = value unless value.nil? || value.empty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
reportedly_scaling.keys.map do |key|
|
80
|
+
find key.sub("scale:", '')
|
81
|
+
end.compact
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Resque
|
2
|
+
module Plugins
|
3
|
+
|
4
|
+
module ResqueHerokuScaler
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def run
|
8
|
+
startup
|
9
|
+
loop do
|
10
|
+
begin
|
11
|
+
scale
|
12
|
+
rescue Exception => e
|
13
|
+
log "Scale failed with #{e.class.name} #{e.message}"
|
14
|
+
end
|
15
|
+
wait_for_scale
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def scale
|
20
|
+
required = scale_for(pending)
|
21
|
+
active = workers
|
22
|
+
|
23
|
+
return if required == active
|
24
|
+
|
25
|
+
log "Scale workers from #{active} to #{required}"
|
26
|
+
|
27
|
+
if required > active
|
28
|
+
scale_workers(required)
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
signal_workers
|
33
|
+
stop = timeout
|
34
|
+
wait_for_workers until ready_to_scale(active) or timeout?(stop)
|
35
|
+
scale_workers(required)
|
36
|
+
|
37
|
+
ensure
|
38
|
+
resume_workers
|
39
|
+
end
|
40
|
+
|
41
|
+
def wait_for_scale
|
42
|
+
sleep Resque::Plugins::ResqueHerokuScaler::Config.scale_interval
|
43
|
+
end
|
44
|
+
|
45
|
+
def wait_for_workers
|
46
|
+
sleep Resque::Plugins::ResqueHerokuScaler::Config.poll_interval
|
47
|
+
end
|
48
|
+
|
49
|
+
def scale_for(pending)
|
50
|
+
Resque::Plugins::ResqueHerokuScaler::Config.scale_for(pending)
|
51
|
+
end
|
52
|
+
|
53
|
+
def scale_workers(qty)
|
54
|
+
Resque::Plugins::ResqueHerokuScaler::Manager.workers = qty
|
55
|
+
end
|
56
|
+
|
57
|
+
def workers
|
58
|
+
Resque::Plugins::ResqueHerokuScaler::Manager.workers
|
59
|
+
end
|
60
|
+
|
61
|
+
def signal_workers
|
62
|
+
Resque.redis.set(:scale, true)
|
63
|
+
end
|
64
|
+
|
65
|
+
def resume_workers
|
66
|
+
Resque.redis.del(:scale)
|
67
|
+
end
|
68
|
+
|
69
|
+
def timeout?(stop)
|
70
|
+
Time.now >= stop
|
71
|
+
end
|
72
|
+
|
73
|
+
def timeout
|
74
|
+
Time.now + Resque::Plugins::ResqueHerokuScaler::Config.scale_timeout
|
75
|
+
end
|
76
|
+
|
77
|
+
def pending
|
78
|
+
Resque.info[:pending].to_i
|
79
|
+
end
|
80
|
+
|
81
|
+
def ready_to_scale(active)
|
82
|
+
Resque.info[:scaling] == active
|
83
|
+
end
|
84
|
+
|
85
|
+
def configure
|
86
|
+
yield Resque::Plugins::ResqueHerokuScaler::Config
|
87
|
+
end
|
88
|
+
|
89
|
+
def startup
|
90
|
+
STDOUT.sync = true
|
91
|
+
trap('TERM') do
|
92
|
+
log "Shutting down scaler"
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
log "Starting scaler"
|
96
|
+
resume_workers
|
97
|
+
end
|
98
|
+
|
99
|
+
def log(message)
|
100
|
+
puts "*** #{message}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/test/config_test.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConfigTest < MiniTest::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@config = Resque::Plugins::ResqueHerokuScaler::Config
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_scale_manager_default
|
9
|
+
assert_equal :heroku, @config.scale_manager
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_scale_interval_default
|
13
|
+
assert_equal 60, @config.scale_interval
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_poll_interval_default
|
17
|
+
assert_equal 5, @config.poll_interval
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_scale_timeout_default
|
21
|
+
assert_equal 90, @config.scale_timeout
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_scale_for_default
|
25
|
+
assert_equal 2, @config.scale_for(20)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_scale_with_default
|
29
|
+
assert_equal nil, @config.scale_with
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_custom_scale_with
|
33
|
+
@config.scale_with = Proc.new { |pending| 99 }
|
34
|
+
assert_equal 99, @config.scale_for(2)
|
35
|
+
@config.scale_with = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_scale_manager
|
39
|
+
@config.scale_manager = :local
|
40
|
+
assert_equal :local, @config.scale_manager
|
41
|
+
@config.scale_manager = :heroku
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_scale_interval
|
45
|
+
@config.scale_interval = 99
|
46
|
+
assert_equal 99, @config.scale_interval
|
47
|
+
@config.scale_interval = 60
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_poll_interval
|
51
|
+
@config.poll_interval = 99
|
52
|
+
assert_equal 99, @config.poll_interval
|
53
|
+
@config.poll_interval = 5
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_scale_timeout
|
57
|
+
@config.scale_timeout = 99
|
58
|
+
assert_equal 99, @config.scale_timeout
|
59
|
+
@config.scale_timeout = 90
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ResqueHerokuScalerTest < MiniTest::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@scaler = Resque::Plugins::ResqueHerokuScaler
|
6
|
+
@config = Resque::Plugins::ResqueHerokuScaler::Config
|
7
|
+
|
8
|
+
@redis = mock('Mock Redis')
|
9
|
+
@redis.stubs(:set).with(:scale, true).returns(true)
|
10
|
+
@redis.stubs(:del).with(:scale).returns(true)
|
11
|
+
Resque.stubs(:redis).returns(@redis)
|
12
|
+
|
13
|
+
@manager = mock('Mock Manager')
|
14
|
+
Resque::Plugins::ResqueHerokuScaler::Manager.stubs(:instance).returns(@manager)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_no_scale_for_zero_jobs
|
18
|
+
Resque.stubs(:info).returns({ :pending => 0, :scaling => 0 })
|
19
|
+
@manager.expects(:workers).returns(0)
|
20
|
+
@manager.expects(:workers=).never
|
21
|
+
@scaler.scale()
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_scale_up_for_pending_job
|
25
|
+
Resque.stubs(:info).returns({ :pending => 1, :scaling => 0 })
|
26
|
+
@manager.expects(:workers).returns(0)
|
27
|
+
@manager.expects(:workers=).with(1)
|
28
|
+
@scaler.scale()
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_scale_down_timeout
|
32
|
+
@config.scale_timeout = 1
|
33
|
+
Resque.stubs(:info).returns({ :pending => 0, :scaling => 0 })
|
34
|
+
@manager.expects(:workers).returns(1)
|
35
|
+
@manager.expects(:workers=).with(0)
|
36
|
+
@scaler.scale()
|
37
|
+
@config.scale_timeout = 90
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_scale_down_for_zero_jobs
|
41
|
+
Resque.stubs(:info).returns({ :pending => 0, :scaling => 1 })
|
42
|
+
@manager.expects(:workers).returns(1)
|
43
|
+
@manager.expects(:workers=).with(0)
|
44
|
+
@scaler.scale()
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_configure
|
48
|
+
@scaler.configure do |c|
|
49
|
+
c.scale_manager = :local
|
50
|
+
c.scale_interval = 30
|
51
|
+
c.poll_interval = 10
|
52
|
+
c.scale_timeout = 20
|
53
|
+
c.scale_with = Proc.new { |pending| 99 }
|
54
|
+
end
|
55
|
+
|
56
|
+
assert_equal :local, @config.scale_manager
|
57
|
+
assert_equal 30, @config.scale_interval
|
58
|
+
assert_equal 10, @config.poll_interval
|
59
|
+
assert_equal 20, @config.scale_timeout
|
60
|
+
assert_equal 99, @config.scale_for(2)
|
61
|
+
|
62
|
+
@config.scale_manager = :heroku
|
63
|
+
@config.scale_interval = 60
|
64
|
+
@config.poll_interval = 5
|
65
|
+
@config.scale_timeout = 90
|
66
|
+
@config.scale_with = nil
|
67
|
+
end
|
68
|
+
end
|
data/test/test_helper.rb
ADDED
data/test/worker_test.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class WorkerTest < MiniTest::Unit::TestCase
|
4
|
+
def test_wait_for_scale
|
5
|
+
Resque.redis.stubs(:exists).with(:scale).returns(true)
|
6
|
+
Resque.redis.expects(:set).with(regexp_matches(/^scale:(.)*$/), true)
|
7
|
+
Resque.redis.expects(:del).with(regexp_matches(/^scale:(.)*$/))
|
8
|
+
|
9
|
+
worker = Resque::Worker.new(['*'])
|
10
|
+
worker.stubs(:shutdown?).returns(true)
|
11
|
+
worker.stubs(:register_worker).returns(true)
|
12
|
+
worker.stubs(:unregister_worker).returns(true)
|
13
|
+
worker.work(0)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_unregister_worker
|
17
|
+
Resque.redis.expects(:del).with(regexp_matches(/^scale:(.)*$/))
|
18
|
+
Resque.redis.stubs(:del).with(regexp_matches(/^worker:(.)*$/))
|
19
|
+
Resque.redis.stubs(:del).with(regexp_matches(/^stat:(.)*$/))
|
20
|
+
|
21
|
+
worker = Resque::Worker.new(['*'])
|
22
|
+
worker.stubs(:shutdown?).returns(true)
|
23
|
+
worker.stubs(:register_worker).returns(true)
|
24
|
+
worker.work(0)
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resque-heroku-scaler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Aaron Dunnington
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-11-27 00:00:00 -05:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: resque
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 1.19.0
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: heroku
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 2.14.0
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
description: " This gem provides autoscaling behavior for Resque jobs on Heroku.\n"
|
39
|
+
email: spirogh@gmail.com
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- LICENSE
|
50
|
+
- HISTORY.md
|
51
|
+
- lib/resque/plugins/resque-heroku-scaler.rb
|
52
|
+
- lib/resque/plugins/resque_heroku_scaler/config.rb
|
53
|
+
- lib/resque/plugins/resque_heroku_scaler/manager/heroku.rb
|
54
|
+
- lib/resque/plugins/resque_heroku_scaler/manager/local.rb
|
55
|
+
- lib/resque/plugins/resque_heroku_scaler/manager.rb
|
56
|
+
- lib/resque/plugins/resque_heroku_scaler/resque.rb
|
57
|
+
- lib/resque/plugins/resque_heroku_scaler/tasks.rb
|
58
|
+
- lib/resque/plugins/resque_heroku_scaler/version.rb
|
59
|
+
- lib/resque/plugins/resque_heroku_scaler/worker.rb
|
60
|
+
- lib/resque/plugins/resque_heroku_scaler.rb
|
61
|
+
- test/config_test.rb
|
62
|
+
- test/resque_heroku_scaler_test.rb
|
63
|
+
- test/test_helper.rb
|
64
|
+
- test/worker_test.rb
|
65
|
+
has_rdoc: true
|
66
|
+
homepage: http://github.com/spiro/resque-heroku-scaler
|
67
|
+
licenses: []
|
68
|
+
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.6.1
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: Resque plugin to autoscale Heroku workers
|
93
|
+
test_files: []
|
94
|
+
|