pvdgm_beanstalk_api 0.1.0
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.
- checksums.yaml +7 -0
- data/README.md +9 -0
- data/Rakefile +38 -0
- data/app/controllers/beanstalk_api/application_controller.rb +68 -0
- data/app/controllers/beanstalk_api/jobs_controller.rb +27 -0
- data/app/controllers/beanstalk_api/tasks_controller.rb +16 -0
- data/app/controllers/beanstalk_api/tubes_controller.rb +149 -0
- data/app/helpers/beanstalk_api/application_helper.rb +4 -0
- data/app/views/layouts/beanstalk_api/application.html.erb +14 -0
- data/config/routes.rb +19 -0
- data/lib/beanstalk_api.rb +4 -0
- data/lib/beanstalk_api/engine.rb +11 -0
- data/lib/beanstalk_api/version.rb +3 -0
- data/lib/tasks/beanstalk_api_tasks.rake +4 -0
- data/spec/controllers/application_controller_spec.rb +87 -0
- data/spec/controllers/jobs_controller_spec.rb +35 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +29 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +38 -0
- data/spec/dummy/config/environments/production.rb +68 -0
- data/spec/dummy/config/environments/test.rb +38 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +5 -0
- data/spec/dummy/db/schema.rb +121 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/spec_helper.rb +40 -0
- metadata +205 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b885363a94242b1a19e7eada2e42aa05a9177b29
|
4
|
+
data.tar.gz: 0d4522c3003e3e923e538c4f3bb5a02f660b95b1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 35ee80c8f48e2f94be681e489f7752f71a7f5f39d8d99b128f2640119f78ba2f27bfd5b43d0f004d0b63ba06dbc9a4c438ed2c8f5687b41fd400a8f5ee92184a
|
7
|
+
data.tar.gz: be7d15b7e2f0b46d2c410f117f3f8549d836a2b308b5238be459cf2d98a589dbb33de90da13f56453afef26c6c0bd0a8fe5b7704ddbe569d549888ebc0646db7
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
|
8
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
9
|
+
load 'rails/tasks/engine.rake'
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'rdoc/task'
|
13
|
+
rescue LoadError
|
14
|
+
require 'rdoc/rdoc'
|
15
|
+
require 'rake/rdoctask'
|
16
|
+
RDoc::Task = Rake::RDocTask
|
17
|
+
end
|
18
|
+
|
19
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'BeanstalkApi'
|
22
|
+
rdoc.options << '--line-numbers'
|
23
|
+
rdoc.rdoc_files.include('README.rdoc')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
Bundler::GemHelper.install_tasks
|
31
|
+
|
32
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
33
|
+
|
34
|
+
require 'rspec/core'
|
35
|
+
require 'rspec/core/rake_task'
|
36
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
37
|
+
RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
38
|
+
task :default => :spec
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module BeanstalkApi
|
2
|
+
|
3
|
+
class ApplicationController < ActionController::Base
|
4
|
+
before_filter :restrict_access
|
5
|
+
|
6
|
+
def authenticated_entity
|
7
|
+
@api_key.try(:authenticateable)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
#
|
13
|
+
# This is different from the standard API authentication because we need to
|
14
|
+
# restrict access to only the API key that is associated with the providigm account.
|
15
|
+
#
|
16
|
+
def restrict_access
|
17
|
+
authorization_header = request.headers['Authorization'] || ''
|
18
|
+
|
19
|
+
if authorization_header =~ /^Basic/
|
20
|
+
authenticate_or_request_with_http_basic do | username, password |
|
21
|
+
@api_key = ApiKey.where(access_token: password).first
|
22
|
+
@api_key.present? && @api_key.authenticateable.id.to_s == username && username == '1'
|
23
|
+
end
|
24
|
+
else
|
25
|
+
authenticate_or_request_with_http_token do |token, options|
|
26
|
+
@api_key = ApiKey.where(access_token: token).first
|
27
|
+
@api_key.present? && @api_key.authenticateable.id.to_s == '1'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def format_job(job)
|
35
|
+
hash = {}
|
36
|
+
if job.is_a?(Hash)
|
37
|
+
job.each_pair do | host, job |
|
38
|
+
hash[job.id] = job.body
|
39
|
+
end
|
40
|
+
else
|
41
|
+
hash[job.id] = job.body
|
42
|
+
end
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def beanstalk_client
|
47
|
+
Ayl::Engine.get_active_engine
|
48
|
+
end
|
49
|
+
|
50
|
+
def pool
|
51
|
+
beanstalk_client.pool
|
52
|
+
end
|
53
|
+
|
54
|
+
def connection
|
55
|
+
pool.open_connections.first
|
56
|
+
end
|
57
|
+
|
58
|
+
def beanstalk_running
|
59
|
+
render json: { error: "Beanstalk not running" } unless beanstalk_client.asynchronous?
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_tube
|
63
|
+
defined?(ASYNC_TUBE) ? ASYNC_TUBE : 'default'
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module BeanstalkApi
|
2
|
+
|
3
|
+
class JobsController < BeanstalkApi::ApplicationController
|
4
|
+
attr_reader :job
|
5
|
+
|
6
|
+
before_filter :must_have_job
|
7
|
+
|
8
|
+
def show
|
9
|
+
render json: { job: format_job(job), statistics: connection.job_stats(params[:id]) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def destroy
|
13
|
+
connection.delete(params[:id])
|
14
|
+
render json: { job: "Job '#{params[:id]}' Deleted" }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def must_have_job
|
20
|
+
@job = pool.peek_job(params[:id])
|
21
|
+
rescue Beanstalk::NotFoundError
|
22
|
+
render json: { error: "Job '#{params[:id]}' not found" }
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module BeanstalkApi
|
2
|
+
|
3
|
+
class TubesController < BeanstalkApi::ApplicationController
|
4
|
+
attr_reader :tube
|
5
|
+
|
6
|
+
before_filter :must_have_existing_tube, only: [ :show, :ready, :delayed, :buried, :worker, :kick ]
|
7
|
+
before_filter :must_have_tube, only: [ :one_job, :all_jobs ]
|
8
|
+
|
9
|
+
respond_to :json
|
10
|
+
|
11
|
+
def index
|
12
|
+
tubes = pool.list_tubes
|
13
|
+
|
14
|
+
render json: tubes.values.flatten
|
15
|
+
end
|
16
|
+
|
17
|
+
def show
|
18
|
+
render json: pool.stats_tube(tube)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ready
|
22
|
+
pool.use(tube)
|
23
|
+
|
24
|
+
job = pool.peek_ready
|
25
|
+
|
26
|
+
render json: { error: "No job in ready state on '#{tube}' tube" } and return if job.nil?
|
27
|
+
|
28
|
+
render json: format_job(job)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delayed
|
32
|
+
pool.use(tube)
|
33
|
+
|
34
|
+
job = pool.peek_delayed
|
35
|
+
|
36
|
+
render json: { error: "No job in delayed state on '#{tube}' tube" } and return if job.nil?
|
37
|
+
|
38
|
+
render json: format_job(job)
|
39
|
+
end
|
40
|
+
|
41
|
+
def buried
|
42
|
+
pool.use(tube)
|
43
|
+
|
44
|
+
job = pool.peek_buried
|
45
|
+
|
46
|
+
render json: { error: "No job in buried state on '#{tube}' tube" } and return if job.nil?
|
47
|
+
|
48
|
+
render json: format_job(job)
|
49
|
+
end
|
50
|
+
|
51
|
+
def one_job
|
52
|
+
pool.watch(tube)
|
53
|
+
|
54
|
+
job = pool.reserve(0)
|
55
|
+
job.delete if job.present?
|
56
|
+
|
57
|
+
render json: { eat_job: "1 job deleted" }
|
58
|
+
rescue Beanstalk::TimedOut
|
59
|
+
render json: { eat_job: "No job to eat on tube '#{tube}'" }
|
60
|
+
end
|
61
|
+
|
62
|
+
def all_jobs
|
63
|
+
count = 0
|
64
|
+
|
65
|
+
pool.watch(tube)
|
66
|
+
|
67
|
+
while ((job = pool.reserve(0)).present?)
|
68
|
+
count += 1
|
69
|
+
job.delete
|
70
|
+
end
|
71
|
+
|
72
|
+
# So, be aware that under ALL circumstances the loop will end with a
|
73
|
+
# Beanstalk::TimedOut exception. We simply catch it and report the
|
74
|
+
# number of jobs eaten. There is no case where this method will not
|
75
|
+
# raise one of these two exceptions.
|
76
|
+
rescue Beanstalk::TimedOut
|
77
|
+
render json: { eat_job: "#{count} job(s) deleted" }
|
78
|
+
end
|
79
|
+
|
80
|
+
def move
|
81
|
+
from_tube = Base64.urlsafe_decode64(params[:id])
|
82
|
+
to_tube = Base64.urlsafe_decode64(params[:to_tube])
|
83
|
+
|
84
|
+
count = 0
|
85
|
+
|
86
|
+
pool.watch(from_tube)
|
87
|
+
pool.use(to_tube)
|
88
|
+
while true
|
89
|
+
job = pool.reserve(0)
|
90
|
+
if job.present?
|
91
|
+
count += 1
|
92
|
+
pool.put(job.body)
|
93
|
+
job.delete
|
94
|
+
else
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# So, be aware that under ALL circumstances the loop will end with a
|
100
|
+
# Beanstalk::TimedOut exception. We simply catch it and report the
|
101
|
+
# number of jobs eaten. There is no case where this method will not
|
102
|
+
# raise one of these two exceptions.
|
103
|
+
rescue Beanstalk::TimedOut
|
104
|
+
render json: { eat_job: "#{count} job(s) moved from '#{from_tube}' to '#{to_tube}'" }
|
105
|
+
rescue Beanstalk::NotFoundError
|
106
|
+
render json: { eat_job: "Tube '#{tube}' not found" }
|
107
|
+
end
|
108
|
+
|
109
|
+
def worker
|
110
|
+
pool.use(tube)
|
111
|
+
pool.put({ type: :ayl, code: 'Kernel.exit' }.to_yaml)
|
112
|
+
render json: { kill_worker: "Put kill worker message on tube '#{tube}'" }
|
113
|
+
end
|
114
|
+
|
115
|
+
def kick
|
116
|
+
bound = params[:bound] || '1'
|
117
|
+
|
118
|
+
pool.use(tube)
|
119
|
+
connection.kick(bound.to_i)
|
120
|
+
|
121
|
+
render json: { kick: "Up to #{bound} jobs were kicked on tube '#{tube}'" }
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def must_have_existing_tube
|
127
|
+
if params[:id]
|
128
|
+
@tube = Base64.urlsafe_decode64(params[:id])
|
129
|
+
begin
|
130
|
+
pool.stats_tube(@tube)
|
131
|
+
rescue Beanstalk::NotFoundError
|
132
|
+
render json: { error: "Specified tube does not exist: #{@tube}" }, status: 404
|
133
|
+
end
|
134
|
+
else
|
135
|
+
render json: { error: "Must specify tube name" }, status: 404
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def must_have_tube
|
140
|
+
if params[:id]
|
141
|
+
@tube = Base64.urlsafe_decode64(params[:id])
|
142
|
+
else
|
143
|
+
render json: { error: "Must specify tube name" }, status: 404
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>BeanstalkApi</title>
|
5
|
+
<%= stylesheet_link_tag "beanstalk_api/application", :media => "all" %>
|
6
|
+
<%= javascript_include_tag "beanstalk_api/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
BeanstalkApi::Engine.routes.draw do
|
2
|
+
|
3
|
+
get '/' => 'tasks#overall_statistics'
|
4
|
+
|
5
|
+
resources :jobs, only: [ :show, :destroy ]
|
6
|
+
|
7
|
+
resources :tubes, only: [ :index, :show ] do
|
8
|
+
member do
|
9
|
+
get 'ready'
|
10
|
+
get 'delayed'
|
11
|
+
get 'buried'
|
12
|
+
delete 'one_job'
|
13
|
+
delete 'all_jobs'
|
14
|
+
delete 'move/:to_tube' => :move
|
15
|
+
delete 'worker'
|
16
|
+
get 'kick(/:bound)' => :kick
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module BeanstalkApi
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace BeanstalkApi
|
4
|
+
config.generators do |g|
|
5
|
+
g.test_framework :rspec, :fixture => false
|
6
|
+
g.fixture_replacement :factory_girl, :dir => 'spec/factories'
|
7
|
+
g.assets false
|
8
|
+
g.helper false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BeanstalkApi::ApplicationController do
|
4
|
+
|
5
|
+
context '#format_job' do
|
6
|
+
|
7
|
+
it "should deal with the specified job if it is a hash" do
|
8
|
+
mock_job1 = double("Job")
|
9
|
+
mock_job1.should_receive(:id).and_return(1)
|
10
|
+
mock_job1.should_receive(:body).and_return('hello world')
|
11
|
+
|
12
|
+
mock_job2 = double("Job")
|
13
|
+
mock_job2.should_receive(:id).and_return(2)
|
14
|
+
mock_job2.should_receive(:body).and_return('goodbye world')
|
15
|
+
|
16
|
+
job = { 'localhost' => mock_job1, 'otherhost' => mock_job2 }
|
17
|
+
|
18
|
+
expect(controller.send(:format_job, job)).to eq( { 1 => 'hello world', 2 => 'goodbye world' } )
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should with the job if it is just a job" do
|
22
|
+
mock_job1 = double("Job")
|
23
|
+
mock_job1.should_receive(:id).and_return(1)
|
24
|
+
mock_job1.should_receive(:body).and_return('hello world')
|
25
|
+
|
26
|
+
expect(controller.send(:format_job, mock_job1)).to eq( { 1 => 'hello world' } )
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
context '#beanstalk_client' do
|
32
|
+
|
33
|
+
it "should retrive the active engine" do
|
34
|
+
mock_engine = double("BSEngine")
|
35
|
+
|
36
|
+
Ayl::Engine.should_receive(:get_active_engine).and_return(mock_engine)
|
37
|
+
|
38
|
+
expect(controller.send(:beanstalk_client)).to be(mock_engine)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
context '#pool' do
|
44
|
+
|
45
|
+
it "should return the pool from the beanstalk client" do
|
46
|
+
mock_pool = double("BeanstalkPool")
|
47
|
+
|
48
|
+
mock_client = double("BeanstalkClient")
|
49
|
+
mock_client.should_receive(:pool).and_return(mock_pool)
|
50
|
+
|
51
|
+
controller.should_receive(:beanstalk_client).and_return(mock_client)
|
52
|
+
|
53
|
+
expect(controller.send(:pool)).to be(mock_pool)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
context '#connection' do
|
59
|
+
|
60
|
+
it "should return the first open connection from the pool" do
|
61
|
+
mock_conn1 = double("BeanstalkConnection")
|
62
|
+
mock_conn2 = double("BeanstalkConnection")
|
63
|
+
|
64
|
+
mock_pool = double("BeanstalkPool")
|
65
|
+
mock_pool.should_receive(:open_connections).and_return([ mock_conn1, mock_conn2 ])
|
66
|
+
|
67
|
+
controller.should_receive(:pool).and_return(mock_pool)
|
68
|
+
|
69
|
+
expect(controller.send(:connection)).to be(mock_conn1)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
context '#default_tube' do
|
75
|
+
|
76
|
+
it "should return 'default' if the ASYNC_TUBE constant is not defined" do
|
77
|
+
# expect(controller.send(:default_tube)).to eq('default')
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return the value of ASYNC_TUBE if ASYNC_TUBE is defined" do
|
81
|
+
ASYNC_TUBE ||= 'stupendous'
|
82
|
+
expect(controller.send(:default_tube)).to eq(ASYNC_TUBE)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|