Skip to content
Browse files

Change the way scalers are loaded

* Previously, the scaler needed to be defined and loaded from
  within the workless gem. Therefore, if you wanted to implement
  a new scaler, you'd have to fork and implement. Now, you can
  defined a scaler by putting it in the Delayed::Workless::Scaler
  module and you should be good to go
* How this works technically:
  - The previous way was to require the scaler from within the gem
    directory, and then instantiate a new instance of the scaler
  - Now, the scalers only have class methods as "callbacks". Since
    the scalers aren't holding content (ie. instance variables),
    there's no need for the scalers to be instantiated
* For scalers that need to talk to heroku, you need to extend the
  scaler to use Delayed::Workless::Scaler::HerokuClient
* Example scaler:

    class Delayed::Workless::Scaler::SuperDuperRandom

      extend Delayed::Workless::Scaler::HerokuClient

      def self.up
        client.set_workers(ENV['APP_NAME'], rand(24)
      end

      def self.down
        client.set_workers(0)
      end

    end
  • Loading branch information...
1 parent 2739fde commit ce6b55063baf67214b501dae56f8bfc8018d399e @bdotdub bdotdub committed with Aug 3, 2011
View
3 .gitignore
@@ -13,6 +13,9 @@ tmtags
## VIM
*.swp
+## Bundler
+.bundle
+
## PROJECT::GENERAL
coverage
rdoc
View
2 .rspec
@@ -0,0 +1,2 @@
+--color
+--format=documentation
View
2 Gemfile
@@ -1,3 +1,3 @@
source :gemcutter
-gemspec
+gemspec
View
36 Rakefile
@@ -20,36 +20,10 @@ rescue LoadError
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
end
-require 'rake/testtask'
-Rake::TestTask.new(:test) do |test|
- test.libs << 'lib' << 'test'
- test.pattern = 'test/**/test_*.rb'
- test.verbose = true
+require 'rspec/core/rake_task'
+desc "Run RSpec"
+RSpec::Core::RakeTask.new do |t|
+ t.verbose = false
end
-begin
- require 'rcov/rcovtask'
- Rcov::RcovTask.new do |test|
- test.libs << 'test'
- test.pattern = 'test/**/test_*.rb'
- test.verbose = true
- end
-rescue LoadError
- task :rcov do
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
- end
-end
-
-task :test => :check_dependencies
-
-task :default => :test
-
-require 'rake/rdoctask'
-Rake::RDocTask.new do |rdoc|
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
-
- rdoc.rdoc_dir = 'rdoc'
- rdoc.title = "workless #{version}"
- rdoc.rdoc_files.include('README*')
- rdoc.rdoc_files.include('lib/**/*.rb')
-end
+task :default => :spec
View
7 lib/workless/railtie.rb
@@ -4,8 +4,9 @@ module Delayed
class Railtie < Rails::Railtie
initializer :after_initialize do
Delayed::Worker.max_attempts = 3
- Delayed::Backend::ActiveRecord::Job.send(:include, Delayed::Workless::Scaler) if defined?(Delayed::Backend::ActiveRecord::Job)
- Delayed::Backend::Mongoid::Job.send(:include, Delayed::Workless::Scaler) if defined?(Delayed::Backend::Mongoid::Job)
+ [ Delayed::Backend::ActiveRecord::Job, Delayed::Backend::Mongoid::Job ].each do |klass|
+ klass.send(:include, Delayed::Workless::Scaler) if defined?(klass)
+ end
end
end
-end
+end
View
15 lib/workless/scaler.rb
@@ -1,9 +1,13 @@
+require 'workless/scalers/heroku'
+require 'workless/scalers/heroku_cedar'
+require 'workless/scalers/local'
+require 'workless/scalers/null'
+
module Delayed
module Workless
module Scaler
def self.included(base)
-
base.send :extend, ClassMethods
base.class_eval do
after_destroy "self.class.scaler.down"
@@ -16,17 +20,14 @@ def self.included(base)
module ClassMethods
def scaler
@scaler ||= if ENV.include?("HEROKU_UPID")
- require File.dirname(__FILE__) + "/scalers/heroku"
- Scaler::Heroku.new
+ Scaler::Heroku
else
- require File.dirname(__FILE__) + "/scalers/local"
- Scaler::Local.new
+ Scaler::Local
end
end
def scaler=(scaler)
- require File.dirname(__FILE__) + "/scalers/#{scaler.to_s}"
- @scaler = "Delayed::Workless::Scaler::#{scaler.to_s.camelize}".constantize.new
+ @scaler = "Delayed::Workless::Scaler::#{scaler.to_s.camelize}".constantize
end
end
View
15 lib/workless/scalers/base.rb
@@ -5,11 +5,20 @@ module Workless
module Scaler
class Base
- def jobs
+ def self.jobs
Delayed::Job.all(:conditions => { :failed_at => nil })
end
end
-
+
+ module HerokuClient
+ require 'heroku'
+
+ def client
+ @client ||= ::Heroku::Client.new(ENV['HEROKU_USER'], ENV['HEROKU_PASSWORD'])
+ end
+
+ end
+
end
end
-end
+end
View
20 lib/workless/scalers/heroku.rb
@@ -6,28 +6,22 @@ module Scaler
class Heroku < Base
- require "heroku"
+ extend Delayed::Workless::Scaler::HerokuClient
- def up
- client.set_workers(ENV['APP_NAME'], 1) if workers == 0
+ def self.up
+ client.set_workers(ENV['APP_NAME'], 1) if self.workers == 0
end
- def down
- client.set_workers(ENV['APP_NAME'], 0) unless workers == 0 or jobs.count > 0
+ def self.down
+ client.set_workers(ENV['APP_NAME'], 0) unless self.workers == 0 or self.jobs.count > 0
end
- def workers
+ def self.workers
client.info(ENV['APP_NAME'])[:workers].to_i
end
- private
-
- def client
- @client ||= ::Heroku::Client.new(ENV['HEROKU_USER'], ENV['HEROKU_PASSWORD'])
- end
-
end
end
end
-end
+end
View
18 lib/workless/scalers/heroku_cedar.rb
@@ -6,26 +6,20 @@ module Scaler
class HerokuCedar < Base
- require "heroku"
+ extend Delayed::Workless::Scaler::HerokuClient
- def up
- client.ps_scale(ENV['APP_NAME'], type: 'worker', qty: 1) if workers == 0
+ def self.up
+ client.ps_scale(ENV['APP_NAME'], :type => 'worker', :qty => 1) if self.workers == 0
end
- def down
- client.ps_scale(ENV['APP_NAME'], type: 'worker', qty: 0) unless workers == 0 or jobs.count > 0
+ def self.down
+ client.ps_scale(ENV['APP_NAME'], :type => 'worker', :qty => 0) unless self.workers == 0 or self.jobs.count > 0
end
- def workers
+ def self.workers
client.ps(ENV['APP_NAME']).count { |p| p["process"] =~ /worker\.\d?/ }
end
- private
-
- def client
- @client ||= ::Heroku::Client.new(ENV['HEROKU_USER'], ENV['HEROKU_PASSWORD'])
- end
-
end
end
View
8 lib/workless/scalers/local.rb
@@ -6,8 +6,8 @@ module Scaler
class Local < Base
- def up
- Rush::Box.new[Rails.root].bash("rake jobs:work", :background => true) if workers == 0
+ def self.up
+ Rush::Box.new[Rails.root].bash("rake jobs:work", :background => true) if self.workers == 0
true
end
@@ -16,12 +16,12 @@ def down
true
end
- def workers
+ def self.workers
Rush::Box.new.processes.filter(:cmdline => /rake jobs:work/).size
end
end
end
end
-end
+end
View
6 lib/workless/scalers/null.rb
@@ -4,14 +4,14 @@ module Scaler
class Null < Base
- def up
+ def self.up
end
- def down
+ def self.down
end
end
end
end
-end
+end
View
35 spec/spec_helper.rb
@@ -0,0 +1,35 @@
+require 'rubygems'
+require 'bundler/setup'
+
+Bundler.require(:default)
+
+require 'workless'
+
+module Delayed
+ module Job
+ class Delayed::Job::Mock
+ def self.after_destroy(method, *args)
+ end
+
+ def self.before_create(method, *args)
+ end
+
+ def self.after_update(method, *args)
+ end
+ end
+ end
+end
+
+class NumWorkers
+ def initialize(count)
+ @count = count
+ end
+
+ def count
+ @count
+ end
+end
+
+Delayed::Job::Mock.send(:include, Delayed::Workless::Scaler)
+
+ENV['APP_NAME'] = 'TestHerokuApp'
View
66 spec/workless/scaler_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Delayed::Workless::Scaler do
+
+ context 'on heroku' do
+
+ before do
+ Delayed::Job::Mock.send(:instance_variable_set, :@scaler, nil)
+ ENV['HEROKU_UPID'] = 'something or other'
+ end
+
+ it 'should be the heroku scaler' do
+ Delayed::Job::Mock.scaler.should == Delayed::Workless::Scaler::Heroku
+ end
+
+ end
+
+ context 'locally' do
+
+ before do
+ Delayed::Job::Mock.send(:instance_variable_set, :@scaler, nil)
+ ENV.delete('HEROKU_UPID')
+ end
+
+ it 'should be the local scaler' do
+ Delayed::Job::Mock.scaler.should == Delayed::Workless::Scaler::Local
+ end
+
+ end
+
+ context 'setting a scaler' do
+
+ context 'with a known scaler' do
+
+ before do
+ Delayed::Job::Mock.scaler = :heroku_logarithmic
+ end
+
+ it 'should be properly assigned' do
+ Delayed::Job::Mock.scaler.should == Delayed::Workless::Scaler::HerokuLogarithmic
+ end
+
+ end
+
+ context 'with a non-workless defined scaler' do
+
+ before do
+ class Delayed::Workless::Scaler::Something < Delayed::Workless::Scaler::Base
+ def self.up
+ end
+ def self.down
+ end
+ end
+
+ Delayed::Job::Mock.scaler = :something
+ end
+
+ it 'should be properly assigned' do
+ Delayed::Job::Mock.scaler.should == Delayed::Workless::Scaler::Something
+ end
+
+ end
+
+ end
+
+end
View
73 spec/workless/scalers/heroku_cedar_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Delayed::Workless::Scaler::HerokuCedar do
+
+ context 'with jobs' do
+
+ before do
+ Delayed::Workless::Scaler::HerokuCedar.should_receive(:jobs).any_number_of_times.and_return(NumWorkers.new(10))
+ end
+
+ context 'without workers' do
+
+ before do
+ Delayed::Workless::Scaler::HerokuCedar.should_receive(:workers).and_return(0)
+ end
+
+ it 'should set the workers to 1' do
+ Delayed::Workless::Scaler::HerokuCedar.client.should_receive(:ps_scale).once.with(ENV['APP_NAME'], :qty => 1, :type => 'worker')
+ Delayed::Workless::Scaler::HerokuCedar.up
+ end
+
+ end
+
+ context 'with workers' do
+
+ before do
+ Delayed::Workless::Scaler::HerokuCedar.should_receive(:workers).and_return(NumWorkers.new(10))
+ end
+
+ it 'should not set anything' do
+ Delayed::Workless::Scaler::HerokuCedar.client.should_not_receive(:ps_scale)
+ Delayed::Workless::Scaler::HerokuCedar.up
+ end
+
+ end
+
+ end
+
+ context 'with no jobs' do
+
+ before do
+ Delayed::Workless::Scaler::HerokuCedar.should_receive(:jobs).any_number_of_times.and_return(NumWorkers.new(0))
+ end
+
+ context 'without workers' do
+
+ before do
+ Delayed::Workless::Scaler::HerokuCedar.should_receive(:workers).and_return(0)
+ end
+
+ it 'should not set anything' do
+ Delayed::Workless::Scaler::HerokuCedar.client.should_not_receive(:ps_scale)
+ Delayed::Workless::Scaler::HerokuCedar.down
+ end
+
+ end
+
+ context 'with workers' do
+
+ before do
+ Delayed::Workless::Scaler::HerokuCedar.should_receive(:workers).and_return(NumWorkers.new(10))
+ end
+
+ it 'should set the workers to 0' do
+ Delayed::Workless::Scaler::HerokuCedar.client.should_receive(:ps_scale).once.with(ENV['APP_NAME'], :qty => 0, :type => 'worker')
+ Delayed::Workless::Scaler::HerokuCedar.down
+ end
+
+ end
+
+ end
+
+end
View
73 spec/workless/scalers/heroku_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Delayed::Workless::Scaler::Heroku do
+
+ context 'with jobs' do
+
+ before do
+ Delayed::Workless::Scaler::Heroku.should_receive(:jobs).any_number_of_times.and_return(NumWorkers.new(10))
+ end
+
+ context 'without workers' do
+
+ before do
+ Delayed::Workless::Scaler::Heroku.should_receive(:workers).and_return(0)
+ end
+
+ it 'should set the workers to 1' do
+ Delayed::Workless::Scaler::Heroku.client.should_receive(:set_workers).once.with(ENV['APP_NAME'], 1)
+ Delayed::Workless::Scaler::Heroku.up
+ end
+
+ end
+
+ context 'with workers' do
+
+ before do
+ Delayed::Workless::Scaler::Heroku.should_receive(:workers).and_return(NumWorkers.new(10))
+ end
+
+ it 'should not set anything' do
+ Delayed::Workless::Scaler::Heroku.client.should_not_receive(:set_workers)
+ Delayed::Workless::Scaler::Heroku.up
+ end
+
+ end
+
+ end
+
+ context 'with no jobs' do
+
+ before do
+ Delayed::Workless::Scaler::Heroku.should_receive(:jobs).any_number_of_times.and_return(NumWorkers.new(0))
+ end
+
+ context 'without workers' do
+
+ before do
+ Delayed::Workless::Scaler::Heroku.should_receive(:workers).and_return(0)
+ end
+
+ it 'should not set anything' do
+ Delayed::Workless::Scaler::Heroku.client.should_not_receive(:set_workers)
+ Delayed::Workless::Scaler::Heroku.down
+ end
+
+ end
+
+ context 'with workers' do
+
+ before do
+ Delayed::Workless::Scaler::Heroku.should_receive(:workers).and_return(NumWorkers.new(10))
+ end
+
+ it 'should set the workers to 0' do
+ Delayed::Workless::Scaler::Heroku.client.should_receive(:set_workers).once.with(ENV['APP_NAME'], 0)
+ Delayed::Workless::Scaler::Heroku.down
+ end
+
+ end
+
+ end
+
+end

0 comments on commit ce6b550

Please sign in to comment.
Something went wrong with that request. Please try again.