roundhouse-x 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +16 -0
- data/3.0-Upgrade.md +70 -0
- data/Changes.md +1127 -0
- data/Gemfile +27 -0
- data/LICENSE +7 -0
- data/README.md +52 -0
- data/Rakefile +9 -0
- data/bin/roundhouse +19 -0
- data/bin/roundhousectl +93 -0
- data/lib/generators/roundhouse/templates/worker.rb.erb +9 -0
- data/lib/generators/roundhouse/templates/worker_spec.rb.erb +6 -0
- data/lib/generators/roundhouse/templates/worker_test.rb.erb +8 -0
- data/lib/generators/roundhouse/worker_generator.rb +49 -0
- data/lib/roundhouse/actor.rb +39 -0
- data/lib/roundhouse/api.rb +859 -0
- data/lib/roundhouse/cli.rb +396 -0
- data/lib/roundhouse/client.rb +210 -0
- data/lib/roundhouse/core_ext.rb +105 -0
- data/lib/roundhouse/exception_handler.rb +30 -0
- data/lib/roundhouse/fetch.rb +154 -0
- data/lib/roundhouse/launcher.rb +98 -0
- data/lib/roundhouse/logging.rb +104 -0
- data/lib/roundhouse/manager.rb +236 -0
- data/lib/roundhouse/middleware/chain.rb +149 -0
- data/lib/roundhouse/middleware/i18n.rb +41 -0
- data/lib/roundhouse/middleware/server/active_record.rb +13 -0
- data/lib/roundhouse/middleware/server/logging.rb +40 -0
- data/lib/roundhouse/middleware/server/retry_jobs.rb +206 -0
- data/lib/roundhouse/monitor.rb +124 -0
- data/lib/roundhouse/paginator.rb +42 -0
- data/lib/roundhouse/processor.rb +159 -0
- data/lib/roundhouse/rails.rb +24 -0
- data/lib/roundhouse/redis_connection.rb +77 -0
- data/lib/roundhouse/scheduled.rb +115 -0
- data/lib/roundhouse/testing/inline.rb +28 -0
- data/lib/roundhouse/testing.rb +193 -0
- data/lib/roundhouse/util.rb +68 -0
- data/lib/roundhouse/version.rb +3 -0
- data/lib/roundhouse/web.rb +264 -0
- data/lib/roundhouse/web_helpers.rb +249 -0
- data/lib/roundhouse/worker.rb +90 -0
- data/lib/roundhouse.rb +177 -0
- data/roundhouse.gemspec +27 -0
- data/test/config.yml +9 -0
- data/test/env_based_config.yml +11 -0
- data/test/fake_env.rb +0 -0
- data/test/fixtures/en.yml +2 -0
- data/test/helper.rb +49 -0
- data/test/test_api.rb +521 -0
- data/test/test_cli.rb +389 -0
- data/test/test_client.rb +294 -0
- data/test/test_exception_handler.rb +55 -0
- data/test/test_fetch.rb +206 -0
- data/test/test_logging.rb +34 -0
- data/test/test_manager.rb +169 -0
- data/test/test_middleware.rb +160 -0
- data/test/test_monitor.rb +258 -0
- data/test/test_processor.rb +176 -0
- data/test/test_rails.rb +23 -0
- data/test/test_redis_connection.rb +127 -0
- data/test/test_retry.rb +390 -0
- data/test/test_roundhouse.rb +87 -0
- data/test/test_scheduled.rb +120 -0
- data/test/test_scheduling.rb +75 -0
- data/test/test_testing.rb +78 -0
- data/test/test_testing_fake.rb +240 -0
- data/test/test_testing_inline.rb +65 -0
- data/test/test_util.rb +18 -0
- data/test/test_web.rb +605 -0
- data/test/test_web_helpers.rb +52 -0
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status/active.png +0 -0
- data/web/assets/images/status/idle.png +0 -0
- data/web/assets/images/status-sd8051fd480.png +0 -0
- data/web/assets/javascripts/application.js +83 -0
- data/web/assets/javascripts/dashboard.js +300 -0
- data/web/assets/javascripts/locales/README.md +27 -0
- data/web/assets/javascripts/locales/jquery.timeago.ar.js +96 -0
- data/web/assets/javascripts/locales/jquery.timeago.bg.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.bs.js +49 -0
- data/web/assets/javascripts/locales/jquery.timeago.ca.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.cs.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.cy.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.da.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.de.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.el.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.en-short.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.en.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.es.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.et.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.fa.js +22 -0
- data/web/assets/javascripts/locales/jquery.timeago.fi.js +28 -0
- data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.fr.js +17 -0
- data/web/assets/javascripts/locales/jquery.timeago.he.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.hr.js +49 -0
- data/web/assets/javascripts/locales/jquery.timeago.hu.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.hy.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.id.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.it.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.ja.js +19 -0
- data/web/assets/javascripts/locales/jquery.timeago.ko.js +17 -0
- data/web/assets/javascripts/locales/jquery.timeago.lt.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.mk.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.nl.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.no.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.pl.js +31 -0
- data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.pt.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.ro.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.rs.js +49 -0
- data/web/assets/javascripts/locales/jquery.timeago.ru.js +34 -0
- data/web/assets/javascripts/locales/jquery.timeago.sk.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.sl.js +44 -0
- data/web/assets/javascripts/locales/jquery.timeago.sv.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.th.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.tr.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.uk.js +34 -0
- data/web/assets/javascripts/locales/jquery.timeago.uz.js +19 -0
- data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +20 -0
- data/web/assets/stylesheets/application.css +746 -0
- data/web/assets/stylesheets/bootstrap.css +9 -0
- data/web/locales/cs.yml +68 -0
- data/web/locales/da.yml +68 -0
- data/web/locales/de.yml +69 -0
- data/web/locales/el.yml +68 -0
- data/web/locales/en.yml +77 -0
- data/web/locales/es.yml +69 -0
- data/web/locales/fr.yml +69 -0
- data/web/locales/hi.yml +75 -0
- data/web/locales/it.yml +69 -0
- data/web/locales/ja.yml +69 -0
- data/web/locales/ko.yml +68 -0
- data/web/locales/nl.yml +68 -0
- data/web/locales/no.yml +69 -0
- data/web/locales/pl.yml +59 -0
- data/web/locales/pt-br.yml +68 -0
- data/web/locales/pt.yml +67 -0
- data/web/locales/ru.yml +75 -0
- data/web/locales/sv.yml +68 -0
- data/web/locales/ta.yml +75 -0
- data/web/locales/zh-cn.yml +68 -0
- data/web/locales/zh-tw.yml +68 -0
- data/web/views/_footer.erb +22 -0
- data/web/views/_job_info.erb +84 -0
- data/web/views/_nav.erb +66 -0
- data/web/views/_paging.erb +23 -0
- data/web/views/_poll_js.erb +5 -0
- data/web/views/_poll_link.erb +7 -0
- data/web/views/_status.erb +4 -0
- data/web/views/_summary.erb +40 -0
- data/web/views/busy.erb +90 -0
- data/web/views/dashboard.erb +75 -0
- data/web/views/dead.erb +34 -0
- data/web/views/layout.erb +31 -0
- data/web/views/morgue.erb +71 -0
- data/web/views/queue.erb +45 -0
- data/web/views/queues.erb +27 -0
- data/web/views/retries.erb +74 -0
- data/web/views/retry.erb +34 -0
- data/web/views/scheduled.erb +54 -0
- data/web/views/scheduled_job_info.erb +8 -0
- metadata +404 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require 'roundhouse/exception_handler'
|
3
|
+
require 'stringio'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
ExceptionHandlerTestException = Class.new(StandardError)
|
7
|
+
TEST_EXCEPTION = ExceptionHandlerTestException.new("Something didn't work!")
|
8
|
+
|
9
|
+
class Component
|
10
|
+
include Roundhouse::ExceptionHandler
|
11
|
+
|
12
|
+
def invoke_exception(args)
|
13
|
+
raise TEST_EXCEPTION
|
14
|
+
rescue ExceptionHandlerTestException => e
|
15
|
+
handle_exception(e,args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TestExceptionHandler < Roundhouse::Test
|
20
|
+
describe "with mock logger" do
|
21
|
+
before do
|
22
|
+
@old_logger = Roundhouse.logger
|
23
|
+
@str_logger = StringIO.new
|
24
|
+
Roundhouse.logger = Logger.new(@str_logger)
|
25
|
+
end
|
26
|
+
|
27
|
+
after do
|
28
|
+
Roundhouse.logger = @old_logger
|
29
|
+
end
|
30
|
+
|
31
|
+
it "logs the exception to Roundhouse.logger" do
|
32
|
+
Component.new.invoke_exception(:a => 1)
|
33
|
+
@str_logger.rewind
|
34
|
+
log = @str_logger.readlines
|
35
|
+
assert_match(/a=>1/, log[0], "didn't include the context")
|
36
|
+
assert_match(/Something didn't work!/, log[1], "didn't include the exception message")
|
37
|
+
assert_match(/test\/test_exception_handler.rb/, log[2], "didn't include the backtrace")
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "when the exception does not have a backtrace" do
|
41
|
+
it "does not fail" do
|
42
|
+
exception = ExceptionHandlerTestException.new
|
43
|
+
assert_nil exception.backtrace
|
44
|
+
|
45
|
+
begin
|
46
|
+
Component.new.handle_exception exception
|
47
|
+
pass
|
48
|
+
rescue StandardError
|
49
|
+
flunk "failed handling a nil backtrace"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
data/test/test_fetch.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require 'roundhouse/fetch'
|
3
|
+
require 'roundhouse/monitor'
|
4
|
+
require 'roundhouse/api'
|
5
|
+
|
6
|
+
class TestFetcher < Roundhouse::Test
|
7
|
+
describe 'fetcher' do
|
8
|
+
before do
|
9
|
+
Roundhouse.redis = { :url => REDIS_URL, :namespace => 'fuzzy' }
|
10
|
+
Roundhouse.redis { |conn| conn.flushdb }
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
Roundhouse.redis = REDIS
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#retrieve_work' do
|
18
|
+
describe 'when nominal' do
|
19
|
+
before do
|
20
|
+
Roundhouse.redis do |conn|
|
21
|
+
conn.lpush(Roundhouse::Monitor::SEMAPHORE, 100)
|
22
|
+
conn.hset(Roundhouse::Monitor.status_bucket(100), 100, Roundhouse::Monitor::ACTIVE)
|
23
|
+
conn.lpush("#{Roundhouse::Monitor::QUEUE}:100", 'msg')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'retrieves' do
|
28
|
+
fetch = Roundhouse::RoundRobinFetch.new
|
29
|
+
uow = fetch.retrieve_work
|
30
|
+
refute_nil uow
|
31
|
+
assert_equal '100', uow.queue_id
|
32
|
+
assert_equal 'msg', uow.message
|
33
|
+
q = Roundhouse::Queue.new(100)
|
34
|
+
assert_equal 0, q.size
|
35
|
+
uow.requeue
|
36
|
+
assert_equal 1, q.size
|
37
|
+
assert_nil uow.acknowledge
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'with empty status' do
|
42
|
+
before do
|
43
|
+
Roundhouse.redis do |conn|
|
44
|
+
conn.lpush(Roundhouse::Monitor::SEMAPHORE, 100)
|
45
|
+
conn.lpush(Roundhouse::Monitor::SEMAPHORE, 200)
|
46
|
+
conn.hset(Roundhouse::Monitor.status_bucket(100), 100, Roundhouse::Monitor::EMPTY)
|
47
|
+
conn.hset(Roundhouse::Monitor.status_bucket(200), 200, Roundhouse::Monitor::ACTIVE)
|
48
|
+
conn.lpush("#{Roundhouse::Monitor::QUEUE}:200", 'msg-200')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'skips queues with empty status' do
|
53
|
+
fetch = Roundhouse::RoundRobinFetch.new
|
54
|
+
uow = fetch.retrieve_work
|
55
|
+
refute_nil uow
|
56
|
+
assert_equal '200', uow.queue_id
|
57
|
+
assert_equal 'msg-200', uow.message
|
58
|
+
q = Roundhouse::Queue.new(200)
|
59
|
+
assert_equal 0, q.size
|
60
|
+
uow.requeue
|
61
|
+
assert_equal 1, q.size
|
62
|
+
assert_nil uow.acknowledge
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'with active status and empty queue' do
|
67
|
+
before do
|
68
|
+
Roundhouse.redis do |conn|
|
69
|
+
conn.lpush(Roundhouse::Monitor::SEMAPHORE, 100)
|
70
|
+
conn.lpush(Roundhouse::Monitor::SEMAPHORE, 200)
|
71
|
+
conn.hset(Roundhouse::Monitor.status_bucket(100), 100, Roundhouse::Monitor::ACTIVE)
|
72
|
+
conn.hset(Roundhouse::Monitor.status_bucket(200), 200, Roundhouse::Monitor::ACTIVE)
|
73
|
+
conn.lpush("#{Roundhouse::Monitor::QUEUE}:200", 'msg-200')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'skips queues with empty status' do
|
78
|
+
empty_queue = Roundhouse::Queue.new(100)
|
79
|
+
assert_equal 0, empty_queue.size
|
80
|
+
assert_equal :active, empty_queue.status
|
81
|
+
|
82
|
+
fetch = Roundhouse::RoundRobinFetch.new
|
83
|
+
uow = fetch.retrieve_work
|
84
|
+
refute_nil uow
|
85
|
+
assert_equal '200', uow.queue_id
|
86
|
+
assert_equal 'msg-200', uow.message
|
87
|
+
|
88
|
+
# After fetching, should set the active status
|
89
|
+
# of an empty queue to empty
|
90
|
+
assert_equal :empty, empty_queue.status
|
91
|
+
|
92
|
+
# The filled queue should act as normal
|
93
|
+
q = Roundhouse::Queue.new(200)
|
94
|
+
assert_equal 0, q.size
|
95
|
+
uow.requeue
|
96
|
+
assert_equal 1, q.size
|
97
|
+
assert_nil uow.acknowledge
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'with suspended status' do
|
102
|
+
before do
|
103
|
+
Roundhouse.redis do |conn|
|
104
|
+
conn.lpush(Roundhouse::Monitor::SEMAPHORE, 100)
|
105
|
+
conn.lpush(Roundhouse::Monitor::SEMAPHORE, 200)
|
106
|
+
conn.hset(Roundhouse::Monitor.status_bucket(100), 100, Roundhouse::Monitor::SUSPENDED)
|
107
|
+
conn.hset(Roundhouse::Monitor.status_bucket(200), 200, Roundhouse::Monitor::ACTIVE)
|
108
|
+
conn.lpush("#{Roundhouse::Monitor::QUEUE}:100", 'msg-100')
|
109
|
+
conn.lpush("#{Roundhouse::Monitor::QUEUE}:200", 'msg-200')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'skips queues with suspended status' do
|
114
|
+
fetch = Roundhouse::RoundRobinFetch.new
|
115
|
+
uow = fetch.retrieve_work
|
116
|
+
refute_nil uow
|
117
|
+
assert_equal '200', uow.queue_id
|
118
|
+
assert_equal 'msg-200', uow.message
|
119
|
+
|
120
|
+
# Should not touch the work in suspended queue
|
121
|
+
suspended_q = Roundhouse::Queue.new(100)
|
122
|
+
assert_equal 1, suspended_q.size
|
123
|
+
|
124
|
+
q = Roundhouse::Queue.new(200)
|
125
|
+
assert_equal 0, q.size
|
126
|
+
uow.requeue
|
127
|
+
assert_equal 1, q.size
|
128
|
+
assert_nil uow.acknowledge
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'bulk requeues' do
|
135
|
+
q1 = Roundhouse::Queue.new(1000)
|
136
|
+
q2 = Roundhouse::Queue.new(2000)
|
137
|
+
assert_equal 0, q1.size
|
138
|
+
assert_equal 0, q2.size
|
139
|
+
uow = Roundhouse::RoundRobinFetch::UnitOfWork
|
140
|
+
Roundhouse::RoundRobinFetch.bulk_requeue([uow.new('fuzzy:queue:1000', 'bob'), uow.new('fuzzy:queue:1000', 'bar'), uow.new('fuzzy:queue:2000', 'widget')], {:queues => []})
|
141
|
+
assert_equal 2, q1.size
|
142
|
+
assert_equal 1, q2.size
|
143
|
+
|
144
|
+
# Changed bulk requeues not to change status
|
145
|
+
# Not sure if this is a good idea yet
|
146
|
+
#assert_equal :active, q1.status
|
147
|
+
#assert_equal :active, q2.status
|
148
|
+
end
|
149
|
+
|
150
|
+
describe 'fetching' do
|
151
|
+
before do
|
152
|
+
Roundhouse::Fetcher.reset
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'instantiates' do
|
156
|
+
begin
|
157
|
+
Roundhouse.options[:fetch] = NullFetch
|
158
|
+
mgr = Minitest::Mock.new
|
159
|
+
fetch = Roundhouse::Fetcher.new(mgr, {})
|
160
|
+
fetch.fetch
|
161
|
+
Roundhouse::Fetcher.done!
|
162
|
+
ensure
|
163
|
+
Roundhouse.options[:fetch] = Roundhouse::RoundRobinFetch
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class NullFetch
|
168
|
+
def initialize(opts)
|
169
|
+
end
|
170
|
+
def retrieve_work
|
171
|
+
end
|
172
|
+
def self.bulk_requeue(*args)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'handles redis network errors' do
|
177
|
+
begin
|
178
|
+
Roundhouse.logger.level = Logger::FATAL
|
179
|
+
Roundhouse.options[:fetch] = ErrorFetch
|
180
|
+
mgr = Minitest::Mock.new
|
181
|
+
fetch = Roundhouse::Fetcher.new(mgr, {})
|
182
|
+
def fetch.pause
|
183
|
+
end
|
184
|
+
refute fetch.down
|
185
|
+
fetch.fetch
|
186
|
+
Roundhouse::Fetcher.done!
|
187
|
+
assert fetch.down
|
188
|
+
ensure
|
189
|
+
Roundhouse.options[:fetch] = Roundhouse::RoundRobinFetch
|
190
|
+
Roundhouse.logger.level = Logger::ERROR
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class ErrorFetch
|
195
|
+
def initialize(opts)
|
196
|
+
end
|
197
|
+
def retrieve_work
|
198
|
+
raise IOError, "ker-BOOM"
|
199
|
+
end
|
200
|
+
def self.bulk_requeue(*args)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require 'roundhouse/logging'
|
3
|
+
|
4
|
+
class TestFetcher < Roundhouse::Test
|
5
|
+
describe Roundhouse::Logging do
|
6
|
+
describe "#with_context" do
|
7
|
+
def context
|
8
|
+
Roundhouse::Logging.logger.formatter.context
|
9
|
+
end
|
10
|
+
|
11
|
+
it "has no context by default" do
|
12
|
+
context.must_equal nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can add a context" do
|
16
|
+
Roundhouse::Logging.with_context "xx" do
|
17
|
+
context.must_equal " xx"
|
18
|
+
end
|
19
|
+
context.must_equal nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "can use multiple contexts" do
|
23
|
+
Roundhouse::Logging.with_context "xx" do
|
24
|
+
context.must_equal " xx"
|
25
|
+
Roundhouse::Logging.with_context "yy" do
|
26
|
+
context.must_equal " xx yy"
|
27
|
+
end
|
28
|
+
context.must_equal " xx"
|
29
|
+
end
|
30
|
+
context.must_equal nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require 'roundhouse/manager'
|
3
|
+
|
4
|
+
class TestManager < Roundhouse::Test
|
5
|
+
|
6
|
+
describe 'manager' do
|
7
|
+
before do
|
8
|
+
Roundhouse.redis = REDIS
|
9
|
+
Roundhouse.redis {|c| c.flushdb }
|
10
|
+
end
|
11
|
+
|
12
|
+
def new_manager(opts)
|
13
|
+
condvar = Minitest::Mock.new
|
14
|
+
condvar.expect(:signal, nil, [])
|
15
|
+
Roundhouse::Manager.new(condvar, opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'creates N processor instances' do
|
19
|
+
mgr = new_manager(options)
|
20
|
+
assert_equal options[:concurrency], mgr.ready.size
|
21
|
+
assert_equal [], mgr.busy
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'assigns work to a processor' do
|
25
|
+
uow = Object.new
|
26
|
+
processor = Minitest::Mock.new
|
27
|
+
processor.expect(:async, processor, [])
|
28
|
+
processor.expect(:process, nil, [uow])
|
29
|
+
|
30
|
+
mgr = new_manager(options)
|
31
|
+
mgr.ready << processor
|
32
|
+
mgr.assign(uow)
|
33
|
+
assert_equal 1, mgr.busy.size
|
34
|
+
|
35
|
+
processor.verify
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'requeues work if stopping' do
|
39
|
+
uow = Minitest::Mock.new
|
40
|
+
uow.expect(:requeue, nil, [])
|
41
|
+
|
42
|
+
mgr = new_manager(options)
|
43
|
+
mgr.fetcher = Roundhouse::RoundRobinFetch.new({:queues => []})
|
44
|
+
mgr.stop
|
45
|
+
mgr.assign(uow)
|
46
|
+
uow.verify
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'shuts down the system' do
|
50
|
+
mgr = new_manager(options)
|
51
|
+
mgr.fetcher = Roundhouse::RoundRobinFetch.new({:queues => []})
|
52
|
+
mgr.stop
|
53
|
+
|
54
|
+
assert mgr.busy.empty?
|
55
|
+
assert mgr.ready.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'returns finished processors to the ready pool' do
|
59
|
+
fetcher = MiniTest::Mock.new
|
60
|
+
fetcher.expect :async, fetcher, []
|
61
|
+
fetcher.expect :fetch, nil, []
|
62
|
+
mgr = new_manager(options)
|
63
|
+
mgr.fetcher = fetcher
|
64
|
+
init_size = mgr.ready.size
|
65
|
+
processor = mgr.ready.pop
|
66
|
+
mgr.busy << processor
|
67
|
+
mgr.processor_done(processor)
|
68
|
+
|
69
|
+
assert_equal 0, mgr.busy.size
|
70
|
+
assert_equal init_size, mgr.ready.size
|
71
|
+
fetcher.verify
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'throws away dead processors' do
|
75
|
+
fetcher = MiniTest::Mock.new
|
76
|
+
fetcher.expect :async, fetcher, []
|
77
|
+
fetcher.expect :fetch, nil, []
|
78
|
+
mgr = new_manager(options)
|
79
|
+
mgr.fetcher = fetcher
|
80
|
+
init_size = mgr.ready.size
|
81
|
+
processor = mgr.ready.pop
|
82
|
+
mgr.busy << processor
|
83
|
+
mgr.processor_died(processor, 'ignored')
|
84
|
+
|
85
|
+
assert_equal 0, mgr.busy.size
|
86
|
+
assert_equal init_size, mgr.ready.size
|
87
|
+
refute mgr.ready.include?(processor)
|
88
|
+
fetcher.verify
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'does not support invalid concurrency' do
|
92
|
+
assert_raises(ArgumentError) { new_manager(concurrency: 0) }
|
93
|
+
assert_raises(ArgumentError) { new_manager(concurrency: -1) }
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'heartbeat' do
|
97
|
+
before do
|
98
|
+
uow = Object.new
|
99
|
+
|
100
|
+
@processor = Minitest::Mock.new
|
101
|
+
@processor.expect(:async, @processor, [])
|
102
|
+
@processor.expect(:process, nil, [uow])
|
103
|
+
|
104
|
+
@mgr = new_manager(options)
|
105
|
+
@mgr.ready << @processor
|
106
|
+
@mgr.assign(uow)
|
107
|
+
|
108
|
+
@processor.verify
|
109
|
+
@proctitle = $0
|
110
|
+
end
|
111
|
+
|
112
|
+
after do
|
113
|
+
$0 = @proctitle
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'when manager is active' do
|
117
|
+
before do
|
118
|
+
Roundhouse::Manager::PROCTITLES << proc { "xyz" }
|
119
|
+
@mgr.heartbeat('identity', heartbeat_data, Roundhouse.dump_json(heartbeat_data))
|
120
|
+
Roundhouse::Manager::PROCTITLES.pop
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'sets useful info to proctitle' do
|
124
|
+
assert_equal "roundhouse #{Roundhouse::VERSION} myapp [1 of 3 busy] xyz", $0
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'stores process info in redis' do
|
128
|
+
info = Roundhouse.redis { |c| c.hmget('identity', 'busy') }
|
129
|
+
assert_equal ["1"], info
|
130
|
+
expires = Roundhouse.redis { |c| c.pttl('identity') }
|
131
|
+
assert_in_delta 60000, expires, 500
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe 'when manager is stopped' do
|
136
|
+
before do
|
137
|
+
@processor.expect(:alive?, [])
|
138
|
+
@processor.expect(:terminate, [])
|
139
|
+
|
140
|
+
@mgr.stop
|
141
|
+
@mgr.processor_done(@processor)
|
142
|
+
@mgr.heartbeat('identity', heartbeat_data, Roundhouse.dump_json(heartbeat_data))
|
143
|
+
|
144
|
+
@processor.verify
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'indicates stopping status in proctitle' do
|
148
|
+
assert_equal "roundhouse #{Roundhouse::VERSION} myapp [0 of 3 busy] stopping", $0
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'stores process info in redis' do
|
152
|
+
info = Roundhouse.redis { |c| c.hmget('identity', 'busy') }
|
153
|
+
assert_equal ["0"], info
|
154
|
+
expires = Roundhouse.redis { |c| c.pttl('identity') }
|
155
|
+
assert_in_delta 60000, expires, 50
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def options
|
161
|
+
{ :concurrency => 3, :queues => ['default'] }
|
162
|
+
end
|
163
|
+
|
164
|
+
def heartbeat_data
|
165
|
+
{ 'concurrency' => 3, 'tag' => 'myapp' }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require 'roundhouse/middleware/chain'
|
3
|
+
require 'roundhouse/processor'
|
4
|
+
require 'roundhouse/fetch'
|
5
|
+
|
6
|
+
class TestMiddleware < Roundhouse::Test
|
7
|
+
describe 'middleware chain' do
|
8
|
+
before do
|
9
|
+
$errors = []
|
10
|
+
Roundhouse.redis = REDIS
|
11
|
+
end
|
12
|
+
|
13
|
+
class CustomMiddleware
|
14
|
+
def initialize(name, recorder)
|
15
|
+
@name = name
|
16
|
+
@recorder = recorder
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(*args)
|
20
|
+
@recorder << [@name, 'before']
|
21
|
+
yield
|
22
|
+
@recorder << [@name, 'after']
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'supports custom middleware' do
|
27
|
+
chain = Roundhouse::Middleware::Chain.new
|
28
|
+
chain.add CustomMiddleware, 1, []
|
29
|
+
|
30
|
+
assert_equal CustomMiddleware, chain.entries.last.klass
|
31
|
+
end
|
32
|
+
|
33
|
+
class CustomWorker
|
34
|
+
$recorder = []
|
35
|
+
include Roundhouse::Worker
|
36
|
+
def perform(recorder)
|
37
|
+
$recorder << ['work_performed']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class NonYieldingMiddleware
|
42
|
+
def call(*args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class AnotherCustomMiddleware
|
47
|
+
def initialize(name, recorder)
|
48
|
+
@name = name
|
49
|
+
@recorder = recorder
|
50
|
+
end
|
51
|
+
|
52
|
+
def call(*args)
|
53
|
+
@recorder << [@name, 'before']
|
54
|
+
yield
|
55
|
+
@recorder << [@name, 'after']
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class YetAnotherCustomMiddleware
|
60
|
+
def initialize(name, recorder)
|
61
|
+
@name = name
|
62
|
+
@recorder = recorder
|
63
|
+
end
|
64
|
+
|
65
|
+
def call(*args)
|
66
|
+
@recorder << [@name, 'before']
|
67
|
+
yield
|
68
|
+
@recorder << [@name, 'after']
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'executes middleware in the proper order' do
|
73
|
+
msg = Roundhouse.dump_json({ 'class' => CustomWorker.to_s, 'args' => [$recorder] })
|
74
|
+
|
75
|
+
Roundhouse.server_middleware do |chain|
|
76
|
+
# should only add once, second should replace the first
|
77
|
+
2.times { |i| chain.add CustomMiddleware, i.to_s, $recorder }
|
78
|
+
chain.insert_before CustomMiddleware, AnotherCustomMiddleware, '2', $recorder
|
79
|
+
chain.insert_after AnotherCustomMiddleware, YetAnotherCustomMiddleware, '3', $recorder
|
80
|
+
end
|
81
|
+
|
82
|
+
boss = Minitest::Mock.new
|
83
|
+
processor = Roundhouse::Processor.new(boss)
|
84
|
+
actor = Minitest::Mock.new
|
85
|
+
actor.expect(:processor_done, nil, [processor])
|
86
|
+
actor.expect(:real_thread, nil, [nil, Thread])
|
87
|
+
boss.expect(:async, actor, [])
|
88
|
+
boss.expect(:async, actor, [])
|
89
|
+
processor.process(Roundhouse::RoundRobinFetch::UnitOfWork.new('queue:100', msg))
|
90
|
+
assert_equal %w(2 before 3 before 1 before work_performed 1 after 3 after 2 after), $recorder.flatten
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'correctly replaces middleware when using middleware with options in the initializer' do
|
94
|
+
chain = Roundhouse::Middleware::Chain.new
|
95
|
+
chain.add Roundhouse::Middleware::Server::RetryJobs
|
96
|
+
chain.add Roundhouse::Middleware::Server::RetryJobs, {:max_retries => 5}
|
97
|
+
assert_equal 1, chain.count
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'correctly prepends middleware' do
|
101
|
+
chain = Roundhouse::Middleware::Chain.new
|
102
|
+
chain_entries = chain.entries
|
103
|
+
chain.add CustomMiddleware
|
104
|
+
chain.prepend YetAnotherCustomMiddleware
|
105
|
+
assert_equal YetAnotherCustomMiddleware, chain_entries.first.klass
|
106
|
+
assert_equal CustomMiddleware, chain_entries.last.klass
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'allows middleware to abruptly stop processing rest of chain' do
|
110
|
+
recorder = []
|
111
|
+
chain = Roundhouse::Middleware::Chain.new
|
112
|
+
chain.add NonYieldingMiddleware
|
113
|
+
chain.add CustomMiddleware, 1, recorder
|
114
|
+
|
115
|
+
final_action = nil
|
116
|
+
chain.invoke { final_action = true }
|
117
|
+
assert_equal nil, final_action
|
118
|
+
assert_equal [], recorder
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe 'i18n' do
|
123
|
+
before do
|
124
|
+
require 'i18n'
|
125
|
+
I18n.enforce_available_locales = false
|
126
|
+
require 'roundhouse/middleware/i18n'
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'saves and restores locale' do
|
130
|
+
I18n.locale = 'fr'
|
131
|
+
msg = {}
|
132
|
+
mw = Roundhouse::Middleware::I18n::Client.new
|
133
|
+
mw.call(nil, msg, nil, nil) { }
|
134
|
+
assert_equal :fr, msg['locale']
|
135
|
+
|
136
|
+
msg['locale'] = 'jp'
|
137
|
+
I18n.locale = I18n.default_locale
|
138
|
+
assert_equal :en, I18n.locale
|
139
|
+
mw = Roundhouse::Middleware::I18n::Server.new
|
140
|
+
mw.call(nil, msg, nil) do
|
141
|
+
assert_equal :jp, I18n.locale
|
142
|
+
end
|
143
|
+
assert_equal :en, I18n.locale
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'supports I18n.enforce_available_locales = true' do
|
147
|
+
I18n.enforce_available_locales = true
|
148
|
+
I18n.available_locales = [:en, :jp]
|
149
|
+
|
150
|
+
msg = { 'locale' => 'jp' }
|
151
|
+
mw = Roundhouse::Middleware::I18n::Server.new
|
152
|
+
mw.call(nil, msg, nil) do
|
153
|
+
assert_equal :jp, I18n.locale
|
154
|
+
end
|
155
|
+
|
156
|
+
I18n.enforce_available_locales = false
|
157
|
+
I18n.available_locales = nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|