heroku-qc-autoscale 0.0.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/.gitignore +5 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/README.md +37 -0
- data/Rakefile +8 -0
- data/heroku-qc-autoscale.gemspec +29 -0
- data/lib/heroku/scaler.rb +49 -0
- data/lib/heroku-qc-autoscale/version.rb +7 -0
- data/lib/heroku-qc-autoscale.rb +35 -0
- data/lib/qc/auto_scale.rb +13 -0
- data/lib/qc/callbacks.rb +28 -0
- data/test/autoscale_test.rb +23 -0
- data/test/heroku/scaler_test.rb +71 -0
- data/test/qc/queue_test.rb +13 -0
- data/test/support/qc_helper.rb +18 -0
- data/test/test_helper.rb +39 -0
- metadata +169 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
Autoscale QueueClassic workers on Heroku
|
2
|
+
==================
|
3
|
+
|
4
|
+
Add to a Rails 3.x project to auto scale QueueClassic workers on heroku.
|
5
|
+
|
6
|
+
|
7
|
+
Usage
|
8
|
+
-----
|
9
|
+
|
10
|
+
Install as gem
|
11
|
+
|
12
|
+
gem install heroku-qc-autoscale
|
13
|
+
|
14
|
+
Add to Gemfile
|
15
|
+
|
16
|
+
gem "heroku-qc-autoscale"
|
17
|
+
|
18
|
+
|
19
|
+
Create config/initializers/qc_autoscale.rb
|
20
|
+
|
21
|
+
Heroku::QC::Autoscale.config do |c|
|
22
|
+
c.api_key = ENV['HEROKU_API_KEY']
|
23
|
+
c.app = ENV['HEROKU_APP']
|
24
|
+
c.scale = [1, 15, 30, 40, 50]
|
25
|
+
c.active = Rails.env.production?
|
26
|
+
end
|
27
|
+
|
28
|
+
Queue jobs as normal with QueueClassic. Based on your scale table, it will recalculate the
|
29
|
+
workers required after each QC#enqueue, and QC#delete.
|
30
|
+
|
31
|
+
QC.enqueue("Time.now")
|
32
|
+
|
33
|
+
|
34
|
+
Meta
|
35
|
+
----
|
36
|
+
|
37
|
+
Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "heroku-qc-autoscale/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "heroku-qc-autoscale"
|
7
|
+
s.version = Heroku::QC::Autoscale::VERSION
|
8
|
+
s.authors = ["David Bradford"]
|
9
|
+
s.email = ["david@zerobearing.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Auto scale your QueueClassic workers on Heroku. Inspired by mirthlab's Resque auto scale gem.}
|
12
|
+
s.description = %q{Add to a Rails 3.x project to auto scale QueueClassic workers on heroku.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "heroku-qc-autoscale"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_runtime_dependency "heroku-api", "~> 0.3.2"
|
23
|
+
s.add_runtime_dependency "activesupport", "~> 3.1.7"
|
24
|
+
s.add_runtime_dependency "i18n", "~> 0.6.0"
|
25
|
+
s.add_runtime_dependency "queue_classic", "~> 2.0.1"
|
26
|
+
|
27
|
+
s.add_development_dependency "minitest", "~> 3.3.0"
|
28
|
+
s.add_development_dependency "pry"
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Heroku
|
2
|
+
class Scaler
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def workers
|
6
|
+
client.get_app(app).body.fetch("workers", 0).to_i
|
7
|
+
end
|
8
|
+
|
9
|
+
def workers=(qty)
|
10
|
+
client.put_workers(app, qty)
|
11
|
+
end
|
12
|
+
|
13
|
+
def job_count
|
14
|
+
::QC::Queries.count.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
# scale workers based on scale
|
18
|
+
def up
|
19
|
+
self.workers = calculate_required_workers unless calculate_required_workers <= workers
|
20
|
+
end
|
21
|
+
|
22
|
+
# shutdown if no jobs exist
|
23
|
+
def down
|
24
|
+
self.workers = 0 if job_count < 1
|
25
|
+
end
|
26
|
+
|
27
|
+
def calculate_required_workers
|
28
|
+
scale.rindex{|x| x <= job_count} + 1
|
29
|
+
end
|
30
|
+
|
31
|
+
# the app to scale
|
32
|
+
def app
|
33
|
+
::Heroku::QC::Autoscale.app
|
34
|
+
end
|
35
|
+
|
36
|
+
# the scale
|
37
|
+
def scale
|
38
|
+
::Heroku::QC::Autoscale.scale || [1, 15, 30, 40, 50]
|
39
|
+
end
|
40
|
+
|
41
|
+
# heroku api client
|
42
|
+
def client
|
43
|
+
@@client ||= ::Heroku::API.new( ::Heroku::QC::Autoscale.heroku_params )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/callbacks'
|
3
|
+
require 'active_support/core_ext/module'
|
4
|
+
require 'queue_classic'
|
5
|
+
require 'heroku-api'
|
6
|
+
|
7
|
+
require "heroku-qc-autoscale/version"
|
8
|
+
|
9
|
+
require "qc/callbacks"
|
10
|
+
require "qc/auto_scale"
|
11
|
+
require "heroku/scaler"
|
12
|
+
|
13
|
+
module Heroku
|
14
|
+
module QC
|
15
|
+
module Autoscale
|
16
|
+
mattr_accessor :api_key, :app, :mock, :scale, :active
|
17
|
+
|
18
|
+
def self.config(&block)
|
19
|
+
yield(self)
|
20
|
+
activate if active == true
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.activate
|
24
|
+
::QC::Queue.send(:include, ::QC::QueueCallbacks)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.heroku_params
|
28
|
+
{
|
29
|
+
api_key: self.api_key || ENV['HEROKU_API_KEY'],
|
30
|
+
mock: self.mock || false
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/qc/callbacks.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module QC
|
2
|
+
module QueueCallbacks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
include ActiveSupport::Callbacks
|
7
|
+
define_callbacks :enqueue, :delete, :scope => [:kind, :name]
|
8
|
+
set_callback :enqueue, :after, QC::AutoScale.new
|
9
|
+
set_callback :delete, :after, QC::AutoScale.new
|
10
|
+
|
11
|
+
alias_method_chain :enqueue, :callbacks
|
12
|
+
alias_method_chain :delete, :callbacks
|
13
|
+
end
|
14
|
+
|
15
|
+
def enqueue_with_callbacks(method, *args)
|
16
|
+
run_callbacks :enqueue do
|
17
|
+
enqueue_without_callbacks(method, *args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete_with_callbacks(id)
|
22
|
+
run_callbacks :delete do
|
23
|
+
delete_without_callbacks(id)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative "./test_helper"
|
2
|
+
|
3
|
+
describe Heroku::QC::Autoscale do
|
4
|
+
|
5
|
+
before do
|
6
|
+
Heroku::QC::Autoscale.config do |c|
|
7
|
+
c.api_key = "123456"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { Heroku::QC::Autoscale }
|
12
|
+
|
13
|
+
it "should have api_key" do
|
14
|
+
subject.api_key.must_equal("123456")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should change api_key at runtime" do
|
18
|
+
subject.api_key.must_equal("123456")
|
19
|
+
subject.api_key = "654321"
|
20
|
+
subject.api_key.must_equal("654321")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative "../test_helper"
|
2
|
+
|
3
|
+
describe Heroku::Scaler do
|
4
|
+
include QCHelper
|
5
|
+
|
6
|
+
QC.define_singleton_method :log do |*args| nil; end # silence QC logger
|
7
|
+
|
8
|
+
subject { Heroku::Scaler }
|
9
|
+
|
10
|
+
it "job_count should be 0" do
|
11
|
+
subject.job_count.must_equal(0)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "#workers" do
|
15
|
+
with_app do |app|
|
16
|
+
subject.workers = 1
|
17
|
+
subject.workers.must_equal(1)
|
18
|
+
|
19
|
+
subject.workers = 2
|
20
|
+
subject.workers.must_equal(2)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "scaling up" do
|
25
|
+
it "with 5 jobs" do
|
26
|
+
with_app do |app|
|
27
|
+
5.times{ QC.enqueue("Time.now") }
|
28
|
+
subject.workers.must_equal(1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "with 16 jobs" do
|
33
|
+
with_app do |app|
|
34
|
+
16.times{ QC.enqueue("Time.now") }
|
35
|
+
subject.workers.must_equal(2)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "with 31 jobs" do
|
40
|
+
with_app do |app|
|
41
|
+
31.times{ QC.enqueue("Time.now") }
|
42
|
+
subject.workers.must_equal(3)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "with 131 jobs" do
|
47
|
+
with_app do |app|
|
48
|
+
131.times{ QC.enqueue("Time.now") }
|
49
|
+
subject.workers.must_equal(5)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "scaling down" do
|
55
|
+
|
56
|
+
it "from 31 workers" do
|
57
|
+
with_app do |app|
|
58
|
+
# add jobs to queue
|
59
|
+
31.times{ QC.enqueue("Time.now") }
|
60
|
+
subject.workers.must_equal(3)
|
61
|
+
|
62
|
+
# do work and scale back down
|
63
|
+
31.times{ QC::Worker.new.work }
|
64
|
+
subject.job_count.must_equal(0)
|
65
|
+
subject.workers.must_equal(0)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative "../test_helper"
|
2
|
+
|
3
|
+
describe QC::Queue do
|
4
|
+
subject { QC::Queue.new("default-test") }
|
5
|
+
|
6
|
+
it "should respond to #enqueue_with_callbacks" do
|
7
|
+
subject.must_respond_to(:enqueue_with_callbacks)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should respond to #delete_with_callbacks" do
|
11
|
+
subject.must_respond_to(:delete_with_callbacks)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module QCHelper
|
2
|
+
|
3
|
+
def setup
|
4
|
+
init_db
|
5
|
+
end
|
6
|
+
|
7
|
+
def teardown
|
8
|
+
QC.delete_all
|
9
|
+
end
|
10
|
+
|
11
|
+
def init_db(table_name="queue_classic_jobs")
|
12
|
+
QC::Conn.execute("SET client_min_messages TO 'warning'")
|
13
|
+
QC::Setup.drop
|
14
|
+
QC::Setup.create
|
15
|
+
QC::Conn.disconnect
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# test/test_helper.rb
|
2
|
+
|
3
|
+
ENV["TEST"] = 'true'
|
4
|
+
ENV["DATABASE_URL"] ||= "postgres:///queue_classic_test"
|
5
|
+
|
6
|
+
$:.unshift File.expand_path("../../lib")
|
7
|
+
require 'rubygems'
|
8
|
+
require 'minitest/autorun'
|
9
|
+
require 'pry'
|
10
|
+
require 'time'
|
11
|
+
|
12
|
+
require 'heroku-qc-autoscale'
|
13
|
+
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
|
14
|
+
|
15
|
+
Heroku::QC::Autoscale.config do |c|
|
16
|
+
c.api_key = "123456"
|
17
|
+
c.app = "racehq-test"
|
18
|
+
c.mock = true
|
19
|
+
c.scale = [1, 15, 30, 40, 50]
|
20
|
+
c.active = true
|
21
|
+
end
|
22
|
+
|
23
|
+
# borrowed from heroku-api test helper
|
24
|
+
def with_app(params={}, &block)
|
25
|
+
params.merge!('name' => Heroku::QC::Autoscale.app) unless params.key?("name")
|
26
|
+
heroku = Heroku::Scaler.client
|
27
|
+
|
28
|
+
begin
|
29
|
+
data = heroku.post_app(params).body
|
30
|
+
@name = data['name']
|
31
|
+
ready = false
|
32
|
+
until ready
|
33
|
+
ready = heroku.request(:method => :put, :path => "/apps/#{@name}/status").status == 201
|
34
|
+
end
|
35
|
+
yield(data)
|
36
|
+
ensure
|
37
|
+
heroku.delete_app(@name) rescue nil
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: heroku-qc-autoscale
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Bradford
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: heroku-api
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.3.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.3.2
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 3.1.7
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 3.1.7
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: i18n
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.6.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.6.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: queue_classic
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.0.1
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.0.1
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: minitest
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 3.3.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 3.3.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: pry
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: Add to a Rails 3.x project to auto scale QueueClassic workers on heroku.
|
111
|
+
email:
|
112
|
+
- david@zerobearing.com
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- .gitignore
|
118
|
+
- .rvmrc
|
119
|
+
- Gemfile
|
120
|
+
- README.md
|
121
|
+
- Rakefile
|
122
|
+
- heroku-qc-autoscale.gemspec
|
123
|
+
- lib/heroku-qc-autoscale.rb
|
124
|
+
- lib/heroku-qc-autoscale/version.rb
|
125
|
+
- lib/heroku/scaler.rb
|
126
|
+
- lib/qc/auto_scale.rb
|
127
|
+
- lib/qc/callbacks.rb
|
128
|
+
- test/autoscale_test.rb
|
129
|
+
- test/heroku/scaler_test.rb
|
130
|
+
- test/qc/queue_test.rb
|
131
|
+
- test/support/qc_helper.rb
|
132
|
+
- test/test_helper.rb
|
133
|
+
homepage: ''
|
134
|
+
licenses: []
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ! '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
segments:
|
146
|
+
- 0
|
147
|
+
hash: -3951171285968165463
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
segments:
|
155
|
+
- 0
|
156
|
+
hash: -3951171285968165463
|
157
|
+
requirements: []
|
158
|
+
rubyforge_project: heroku-qc-autoscale
|
159
|
+
rubygems_version: 1.8.21
|
160
|
+
signing_key:
|
161
|
+
specification_version: 3
|
162
|
+
summary: Auto scale your QueueClassic workers on Heroku. Inspired by mirthlab's Resque
|
163
|
+
auto scale gem.
|
164
|
+
test_files:
|
165
|
+
- test/autoscale_test.rb
|
166
|
+
- test/heroku/scaler_test.rb
|
167
|
+
- test/qc/queue_test.rb
|
168
|
+
- test/support/qc_helper.rb
|
169
|
+
- test/test_helper.rb
|