roundhouse-x 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/.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
|