dyno_scaler 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.
- data/.gitignore +16 -0
- data/.rspec +4 -0
- data/.rvmrc +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +158 -0
- data/LICENSE.txt +22 -0
- data/README.md +103 -0
- data/Rakefile +8 -0
- data/dyno_scaler.gemspec +26 -0
- data/lib/dyno_scaler/configuration.rb +110 -0
- data/lib/dyno_scaler/engine.rb +7 -0
- data/lib/dyno_scaler/heroku.rb +20 -0
- data/lib/dyno_scaler/manager.rb +77 -0
- data/lib/dyno_scaler/version.rb +3 -0
- data/lib/dyno_scaler/workers/resque.rb +64 -0
- data/lib/dyno_scaler/workers.rb +7 -0
- data/lib/dyno_scaler.rb +27 -0
- data/spec/dyno_scaler/configuration_spec.rb +73 -0
- data/spec/dyno_scaler/heroku_spec.rb +22 -0
- data/spec/dyno_scaler/manager_spec.rb +349 -0
- data/spec/dyno_scaler/workers/resque_spec.rb +201 -0
- data/spec/dyno_scaler_spec.rb +11 -0
- data/spec/spec_helper.rb +40 -0
- metadata +155 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe DynoScaler::Configuration do
|
|
4
|
+
subject(:config) { described_class.new }
|
|
5
|
+
|
|
6
|
+
it "defaults max_workers to 1" do
|
|
7
|
+
config.max_workers.should eq(1)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "defaults min_workers to 0" do
|
|
11
|
+
config.min_workers.should eq(0)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "defaults to not enabled" do
|
|
15
|
+
config.enabled.should be_false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "defaults job_worker_ratio to { 1 => 1, 2 => 25, 3 => 50, 4 => 75, 5 => 100 }" do
|
|
19
|
+
config.job_worker_ratio.should eq({
|
|
20
|
+
1 => 1,
|
|
21
|
+
2 => 25,
|
|
22
|
+
3 => 50,
|
|
23
|
+
4 => 75,
|
|
24
|
+
5 => 100
|
|
25
|
+
})
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "defaults application to nil" do
|
|
29
|
+
config.application.should be_nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "when HEROKU_API_KEY environment variable is configured" do
|
|
33
|
+
before { ENV['HEROKU_API_KEY'] = 'some-api-key' }
|
|
34
|
+
after { ENV['HEROKU_API_KEY'] = nil }
|
|
35
|
+
|
|
36
|
+
it "defaults to enabled" do
|
|
37
|
+
config.should be_enabled
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context "when HEROKU_APP environment variable is configured" do
|
|
42
|
+
before { ENV['HEROKU_APP'] = 'my-app-on-heroku' }
|
|
43
|
+
after { ENV['HEROKU_APP'] = nil }
|
|
44
|
+
|
|
45
|
+
it "defaults application to the value of HEROKU_APP" do
|
|
46
|
+
config.application.should eq(ENV['HEROKU_APP'])
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "async" do
|
|
51
|
+
it "defaults to false" do
|
|
52
|
+
config.async.should be_false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
context "when set to true" do
|
|
56
|
+
before { config.async = true }
|
|
57
|
+
|
|
58
|
+
it "configures a GirlFriday-callable" do
|
|
59
|
+
config.async.should respond_to(:call)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context "when configured with a block" do
|
|
64
|
+
before do
|
|
65
|
+
config.async { :ok }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "configures the callable" do
|
|
69
|
+
config.async.call.should eq(:ok)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe DynoScaler::Heroku do
|
|
4
|
+
let(:application) { 'my-application-on-heroku' }
|
|
5
|
+
subject(:heroku_client) { DynoScaler::Heroku.new application }
|
|
6
|
+
|
|
7
|
+
let(:heroku_api) { mock(::Heroku::API, :post_ps_scale => false) }
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
::Heroku::API.stub!(:new).and_return(heroku_api)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "scaling workers" do
|
|
14
|
+
let(:quantity) { 2 }
|
|
15
|
+
|
|
16
|
+
it "scales workers of the application to the given number of workers" do
|
|
17
|
+
heroku_api.should_receive(:post_ps_scale).with(application, 'worker', quantity)
|
|
18
|
+
heroku_client.scale_workers(quantity)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe DynoScaler::Manager do
|
|
4
|
+
let(:config) { DynoScaler.configuration }
|
|
5
|
+
|
|
6
|
+
subject(:manager) { DynoScaler::Manager.new }
|
|
7
|
+
|
|
8
|
+
let(:heroku) { mock(DynoScaler::Heroku, scale_workers: false) }
|
|
9
|
+
|
|
10
|
+
let(:workers) { 0 }
|
|
11
|
+
let(:pending_jobs) { 0 }
|
|
12
|
+
let(:running_jobs) { 0 }
|
|
13
|
+
|
|
14
|
+
let(:options) do
|
|
15
|
+
{
|
|
16
|
+
workers: workers,
|
|
17
|
+
pending: pending_jobs,
|
|
18
|
+
working: running_jobs
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
before do
|
|
23
|
+
DynoScaler.reset!
|
|
24
|
+
DynoScaler.configuration.logger = Logger.new(StringIO.new)
|
|
25
|
+
|
|
26
|
+
config.max_workers = 5
|
|
27
|
+
config.application = 'my-app'
|
|
28
|
+
config.enabled = true
|
|
29
|
+
|
|
30
|
+
DynoScaler::Heroku.stub!(:new).with(config.application).and_return(heroku)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
shared_examples_for "disabled" do
|
|
34
|
+
before { config.enabled = false }
|
|
35
|
+
|
|
36
|
+
it "does nothing" do
|
|
37
|
+
heroku.should_not_receive(:scale_workers)
|
|
38
|
+
perform_action
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "scale up" do
|
|
43
|
+
def perform_action
|
|
44
|
+
manager.scale_up(options)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context "when there are no workers running" do
|
|
48
|
+
context "and there is no pending jobs" do
|
|
49
|
+
it "does nothing" do
|
|
50
|
+
heroku.should_not_receive(:scale_workers)
|
|
51
|
+
perform_action
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
DynoScaler.configuration.job_worker_ratio.keys.each do |number_of_workers|
|
|
56
|
+
context "and there is enough pending jobs so as to scale #{number_of_workers} workers" do
|
|
57
|
+
let(:pending_jobs) { config.job_worker_ratio[number_of_workers] }
|
|
58
|
+
|
|
59
|
+
it "scales workers to #{number_of_workers}" do
|
|
60
|
+
heroku.should_receive(:scale_workers).with(number_of_workers)
|
|
61
|
+
perform_action
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it_should_behave_like "disabled"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "when there is one worker running" do
|
|
70
|
+
let(:workers) { 1 }
|
|
71
|
+
|
|
72
|
+
context "and there is no pending jobs" do
|
|
73
|
+
it "does nothing" do
|
|
74
|
+
heroku.should_not_receive(:scale_workers)
|
|
75
|
+
perform_action
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context "and there is less pending jobs that would require another worker" do
|
|
80
|
+
let(:pending_jobs) { config.job_worker_ratio[2] - 1 }
|
|
81
|
+
|
|
82
|
+
it "does nothing" do
|
|
83
|
+
heroku.should_not_receive(:scale_workers)
|
|
84
|
+
perform_action
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context "and there is enough pending jobs as to scale another worker" do
|
|
89
|
+
let(:pending_jobs) { config.job_worker_ratio[2] }
|
|
90
|
+
|
|
91
|
+
it "scales workers" do
|
|
92
|
+
heroku.should_receive(:scale_workers).with(2)
|
|
93
|
+
perform_action
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context "when there are many workers running" do
|
|
99
|
+
let(:workers) { 4 }
|
|
100
|
+
|
|
101
|
+
context "and there is no pending jobs" do
|
|
102
|
+
it "does nothing" do
|
|
103
|
+
heroku.should_not_receive(:scale_workers)
|
|
104
|
+
perform_action
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context "and there is less pending jobs that would require another worker" do
|
|
109
|
+
let(:pending_jobs) { config.job_worker_ratio[4] - 1 }
|
|
110
|
+
|
|
111
|
+
it "does nothing" do
|
|
112
|
+
heroku.should_not_receive(:scale_workers)
|
|
113
|
+
perform_action
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
context "and there is enough pending jobs as to scale another worker" do
|
|
118
|
+
let(:pending_jobs) { config.job_worker_ratio[5] }
|
|
119
|
+
|
|
120
|
+
it "scales workers" do
|
|
121
|
+
heroku.should_receive(:scale_workers).with(5)
|
|
122
|
+
perform_action
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
context "and it is the maximum number of workers running" do
|
|
127
|
+
before { config.max_workers = workers }
|
|
128
|
+
|
|
129
|
+
context "and there is enough pending jobs as to scale another worker" do
|
|
130
|
+
let(:pending_jobs) { config.job_worker_ratio[5] }
|
|
131
|
+
|
|
132
|
+
it "does nothing" do
|
|
133
|
+
heroku.should_not_receive(:scale_workers)
|
|
134
|
+
perform_action
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
describe "scale down" do
|
|
142
|
+
def perform_action
|
|
143
|
+
manager.scale_down(options)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
context "when there are no workers running" do
|
|
147
|
+
it "does nothing" do
|
|
148
|
+
heroku.should_not_receive(:scale_workers)
|
|
149
|
+
perform_action
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
context "when there is one worker running" do
|
|
154
|
+
let(:workers) { 1 }
|
|
155
|
+
|
|
156
|
+
context "and there are no pending jobs" do
|
|
157
|
+
context "and no running jobs" do
|
|
158
|
+
it "scales down" do
|
|
159
|
+
heroku.should_receive(:scale_workers).with(config.min_workers)
|
|
160
|
+
perform_action
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
context "when min_workers is configured with a different value" do
|
|
164
|
+
before { config.min_workers = 1 }
|
|
165
|
+
|
|
166
|
+
it "does nothing" do
|
|
167
|
+
heroku.should_not_receive(:scale_workers)
|
|
168
|
+
perform_action
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context "but there are many running jobs" do
|
|
174
|
+
let(:running_jobs) { 4 }
|
|
175
|
+
|
|
176
|
+
it "does nothing" do
|
|
177
|
+
heroku.should_not_receive(:scale_workers)
|
|
178
|
+
perform_action
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
context "and there are pending jobs" do
|
|
184
|
+
let(:pending_jobs) { 1 }
|
|
185
|
+
|
|
186
|
+
context "and no running jobs" do
|
|
187
|
+
it "does nothing" do
|
|
188
|
+
heroku.should_not_receive(:scale_workers)
|
|
189
|
+
perform_action
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
context "but there are running jobs" do
|
|
194
|
+
let(:running_jobs) { 1 }
|
|
195
|
+
|
|
196
|
+
it "does nothing" do
|
|
197
|
+
heroku.should_not_receive(:scale_workers)
|
|
198
|
+
perform_action
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
context "when there are many workers running" do
|
|
205
|
+
let(:workers) { 4 }
|
|
206
|
+
|
|
207
|
+
context "and there are no pending jobs" do
|
|
208
|
+
context "and no running jobs" do
|
|
209
|
+
it "scales down" do
|
|
210
|
+
heroku.should_receive(:scale_workers).with(config.min_workers)
|
|
211
|
+
perform_action
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
context "when min_workers is configured with a different value" do
|
|
215
|
+
before { config.min_workers = 2 }
|
|
216
|
+
|
|
217
|
+
it "scales down to the min workers value" do
|
|
218
|
+
heroku.should_receive(:scale_workers).with(config.min_workers)
|
|
219
|
+
perform_action
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
context "but there are many running jobs" do
|
|
225
|
+
let(:running_jobs) { 4 }
|
|
226
|
+
|
|
227
|
+
it "does nothing" do
|
|
228
|
+
heroku.should_not_receive(:scale_workers)
|
|
229
|
+
perform_action
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
context "and there are pending jobs" do
|
|
235
|
+
let(:pending_jobs) { 1 }
|
|
236
|
+
|
|
237
|
+
context "and no running jobs" do
|
|
238
|
+
it "does nothing" do
|
|
239
|
+
heroku.should_not_receive(:scale_workers)
|
|
240
|
+
perform_action
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
context "but there are running jobs" do
|
|
245
|
+
let(:running_jobs) { 1 }
|
|
246
|
+
|
|
247
|
+
it "does nothing" do
|
|
248
|
+
heroku.should_not_receive(:scale_workers)
|
|
249
|
+
perform_action
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
describe "scale with options" do
|
|
257
|
+
let(:action) { :scale_up }
|
|
258
|
+
let(:options) do
|
|
259
|
+
{
|
|
260
|
+
action: action,
|
|
261
|
+
workers: workers,
|
|
262
|
+
pending: pending_jobs,
|
|
263
|
+
working: running_jobs
|
|
264
|
+
}
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def perform_action
|
|
268
|
+
manager.scale_with(options)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
context "when action is scale up" do
|
|
272
|
+
it "scales up passing options" do
|
|
273
|
+
manager.should_receive(:scale_up).with(options)
|
|
274
|
+
perform_action
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
context "when action is scale down" do
|
|
279
|
+
let(:action) { :scale_down }
|
|
280
|
+
|
|
281
|
+
it "scales down passing options" do
|
|
282
|
+
manager.should_receive(:scale_down).with(options)
|
|
283
|
+
perform_action
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
context "when no action is provided" do
|
|
288
|
+
let(:options) do
|
|
289
|
+
{
|
|
290
|
+
workers: workers,
|
|
291
|
+
pending: pending_jobs,
|
|
292
|
+
working: running_jobs
|
|
293
|
+
}
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
context "when there are no workers running" do
|
|
297
|
+
context "and there is no pending jobs" do
|
|
298
|
+
it "does nothing" do
|
|
299
|
+
manager.should_not_receive(:scale_up)
|
|
300
|
+
manager.should_not_receive(:scale_down)
|
|
301
|
+
perform_action
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
context "and there is pending jobs" do
|
|
306
|
+
let(:pending_jobs) { 2 }
|
|
307
|
+
|
|
308
|
+
it "scales up" do
|
|
309
|
+
manager.should_receive(:scale_up).with(options)
|
|
310
|
+
perform_action
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
context "when there are workers running" do
|
|
316
|
+
let(:workers) { 4 }
|
|
317
|
+
|
|
318
|
+
context "and there are no pending jobs" do
|
|
319
|
+
context "and no running jobs" do
|
|
320
|
+
it "scales down" do
|
|
321
|
+
manager.should_receive(:scale_down).with(options)
|
|
322
|
+
perform_action
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
context "but there are many running jobs" do
|
|
327
|
+
let(:running_jobs) { 4 }
|
|
328
|
+
|
|
329
|
+
it "does nothing" do
|
|
330
|
+
manager.should_not_receive(:scale_up)
|
|
331
|
+
manager.should_not_receive(:scale_down)
|
|
332
|
+
perform_action
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
context "and there are pending jobs" do
|
|
338
|
+
let(:pending_jobs) { 1 }
|
|
339
|
+
|
|
340
|
+
it "does nothing" do
|
|
341
|
+
manager.should_not_receive(:scale_up)
|
|
342
|
+
manager.should_not_receive(:scale_down)
|
|
343
|
+
perform_action
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
require 'resque'
|
|
4
|
+
|
|
5
|
+
class SampleJob
|
|
6
|
+
include DynoScaler::Workers::Resque
|
|
7
|
+
|
|
8
|
+
@queue = :sample
|
|
9
|
+
|
|
10
|
+
def self.perform
|
|
11
|
+
# do something
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.reset!
|
|
15
|
+
@manager = nil
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe DynoScaler::Workers::Resque do
|
|
20
|
+
let(:manager) { mock(DynoScaler::Manager, scale_up: false, scale_down: false) }
|
|
21
|
+
|
|
22
|
+
let(:workers) { 0 }
|
|
23
|
+
let(:working) { 0 }
|
|
24
|
+
let(:pending) { 1 }
|
|
25
|
+
|
|
26
|
+
before do
|
|
27
|
+
Resque.stub!(:info).and_return({
|
|
28
|
+
workers: workers,
|
|
29
|
+
working: working,
|
|
30
|
+
pending: pending
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
SampleJob.reset!
|
|
34
|
+
DynoScaler::Manager.stub!(:new).and_return(manager)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def work_off(queue)
|
|
38
|
+
job = Resque::Job.reserve(queue)
|
|
39
|
+
job ? job.perform : fail("No jobs for queue '#{queue}'.")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "when enqueued" do
|
|
43
|
+
it "scales up" do
|
|
44
|
+
manager.should_receive(:scale_up).with(Resque.info)
|
|
45
|
+
Resque.enqueue(SampleJob)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context "when there are workers" do
|
|
49
|
+
let(:workers) { 2 }
|
|
50
|
+
|
|
51
|
+
it "passes the number of current workers to the manager" do
|
|
52
|
+
manager.should_receive(:scale_up).with(Resque.info)
|
|
53
|
+
Resque.enqueue(SampleJob)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "when there are pending jobs" do
|
|
58
|
+
let(:pending) { 5 }
|
|
59
|
+
|
|
60
|
+
it "passes the number of pending jobs" do
|
|
61
|
+
manager.should_receive(:scale_up).with(Resque.info)
|
|
62
|
+
Resque.enqueue(SampleJob)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context "when scaling up is disabled" do
|
|
67
|
+
before { SampleJob.disable_scaling_up }
|
|
68
|
+
after { SampleJob.enable_scaling_up }
|
|
69
|
+
|
|
70
|
+
it "does not scale up" do
|
|
71
|
+
manager.should_not_receive(:scale_up)
|
|
72
|
+
Resque.enqueue(SampleJob)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context "when an error occurs while scaling up" do
|
|
77
|
+
before do
|
|
78
|
+
manager.stub!(:scale_up).and_raise("error")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "does not raises it" do
|
|
82
|
+
capture(:stderr) do
|
|
83
|
+
expect { Resque.enqueue(SampleJob) }.to_not raise_error
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "enqueues the job" do
|
|
88
|
+
capture(:stderr) do
|
|
89
|
+
Resque.enqueue(SampleJob)
|
|
90
|
+
work_off(:sample)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "prints a message in $stderr" do
|
|
95
|
+
capture(:stderr) { Resque.enqueue(SampleJob) }.should eq("Could not scale up workers: error\n")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context "and async is configured" do
|
|
100
|
+
let(:config) { DynoScaler.configuration }
|
|
101
|
+
|
|
102
|
+
context "with default processor" do
|
|
103
|
+
before { config.async = true }
|
|
104
|
+
|
|
105
|
+
it "calls the given async processor passing the current Resque info and the scale up action" do
|
|
106
|
+
config.async.should_receive(:call).with(Resque.info.merge(action: :scale_up))
|
|
107
|
+
Resque.enqueue(SampleJob)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context "and the Girl-Friday job is run" do
|
|
111
|
+
before { GirlFriday::Queue.immediate! }
|
|
112
|
+
|
|
113
|
+
after do
|
|
114
|
+
GirlFriday::Queue.queue!
|
|
115
|
+
DynoScaler.reset!
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "runs the scale up with the Resque info and the scale up action" do
|
|
119
|
+
DynoScaler.manager.should_receive(:scale_with).with(Resque.info.merge(action: :scale_up))
|
|
120
|
+
Resque.enqueue(SampleJob)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
context "with a block" do
|
|
126
|
+
before do
|
|
127
|
+
config.async { |options| :ok }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "calls the given async processor passing the current Resque info and the scale up action" do
|
|
131
|
+
config.async.should_receive(:call).with(Resque.info.merge(action: :scale_up))
|
|
132
|
+
Resque.enqueue(SampleJob)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe "after performing" do
|
|
139
|
+
before do
|
|
140
|
+
Resque.enqueue(SampleJob)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "scales down" do
|
|
144
|
+
manager.should_receive(:scale_down).with(Resque.info)
|
|
145
|
+
work_off(:sample)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
context "when there are workers" do
|
|
149
|
+
let(:workers) { 2 }
|
|
150
|
+
|
|
151
|
+
it "passes the number of current workers to the manager" do
|
|
152
|
+
manager.should_receive(:scale_down).with(Resque.info)
|
|
153
|
+
work_off(:sample)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
context "when there are pending jobs" do
|
|
158
|
+
let(:pending) { 5 }
|
|
159
|
+
|
|
160
|
+
it "passes the number of pending jobs" do
|
|
161
|
+
manager.should_receive(:scale_down).with(Resque.info)
|
|
162
|
+
work_off(:sample)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
context "when there are running jobs" do
|
|
167
|
+
let(:working) { 5 }
|
|
168
|
+
|
|
169
|
+
it "passes the number of running jobs minus 1, since we do not count ourselves" do
|
|
170
|
+
manager.should_receive(:scale_down).with(Resque.info.merge(working: Resque.info[:working] - 1))
|
|
171
|
+
work_off(:sample)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
context "when scaling down is disabled" do
|
|
176
|
+
before { SampleJob.disable_scaling_down }
|
|
177
|
+
after { SampleJob.enable_scaling_down }
|
|
178
|
+
|
|
179
|
+
it "does not scale down" do
|
|
180
|
+
manager.should_not_receive(:scale_down)
|
|
181
|
+
work_off(:sample)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
context "when an error occurs while scaling down" do
|
|
186
|
+
before do
|
|
187
|
+
manager.stub!(:scale_down).and_raise("error")
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it "does not raises it" do
|
|
191
|
+
capture(:stderr) do
|
|
192
|
+
expect { work_off(:sample) }.to_not raise_error
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "prints a message in $stderr" do
|
|
197
|
+
capture(:stderr) { work_off(:sample) }.should eq("Could not scale down workers: error\n")
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe DynoScaler do
|
|
4
|
+
it "returns a new configuration" do
|
|
5
|
+
DynoScaler.configuration.should be_a(DynoScaler::Configuration)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "returns a new manager" do
|
|
9
|
+
DynoScaler.manager.should be_a(DynoScaler::Manager)
|
|
10
|
+
end
|
|
11
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'simplecov'
|
|
2
|
+
|
|
3
|
+
SimpleCov.start do
|
|
4
|
+
add_filter 'spec'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require 'bundler/setup'
|
|
8
|
+
|
|
9
|
+
ENV['RACK_ENV'] ||= 'test'
|
|
10
|
+
|
|
11
|
+
Bundler.require :default, ENV['RACK_ENV']
|
|
12
|
+
|
|
13
|
+
RSpec.configure do |config|
|
|
14
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
|
15
|
+
config.run_all_when_everything_filtered = true
|
|
16
|
+
config.filter_run :focus
|
|
17
|
+
|
|
18
|
+
# Run specs in random order to surface order dependencies. If you find an
|
|
19
|
+
# order dependency and want to debug it, you can fix the order by providing
|
|
20
|
+
# the seed, which is printed after each run.
|
|
21
|
+
# --seed 1234
|
|
22
|
+
# config.order = 'random'
|
|
23
|
+
|
|
24
|
+
config.before do
|
|
25
|
+
DynoScaler.configuration.logger = Logger.new(StringIO.new)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def capture(stream)
|
|
29
|
+
begin
|
|
30
|
+
stream = stream.to_s
|
|
31
|
+
eval "$#{stream} = StringIO.new"
|
|
32
|
+
yield
|
|
33
|
+
result = eval("$#{stream}").string
|
|
34
|
+
ensure
|
|
35
|
+
eval("$#{stream} = #{stream.upcase}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
end
|