resque-heroku-scaler 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/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
|
+
|