heroku-vector 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +95 -0
- data/README.md +171 -0
- data/Rakefile +10 -0
- data/bin/heroku_vector +91 -0
- data/config.rb.example +28 -0
- data/diagrams/Architecture.pptx +0 -0
- data/diagrams/Architecture/Slide1.png +0 -0
- data/examples/upstart_conf +10 -0
- data/heroku-vector.gemspec +38 -0
- data/lib/heroku_vector.rb +133 -0
- data/lib/heroku_vector/dyno_scaler.rb +133 -0
- data/lib/heroku_vector/engine/heroku.rb +63 -0
- data/lib/heroku_vector/helper.rb +23 -0
- data/lib/heroku_vector/process_manager.rb +61 -0
- data/lib/heroku_vector/sampler.rb +50 -0
- data/lib/heroku_vector/source/new_relic.rb +50 -0
- data/lib/heroku_vector/source/sidekiq.rb +49 -0
- data/lib/heroku_vector/version.rb +3 -0
- data/lib/heroku_vector/worker.rb +49 -0
- data/test/fixtures/vcr_cassettes/heroku_dyno_types.yml +69 -0
- data/test/fixtures/vcr_cassettes/heroku_scale_dynos.yml +65 -0
- data/test/fixtures/vcr_cassettes/newrelic_account.yml +78 -0
- data/test/fixtures/vcr_cassettes/newrelic_app.yml +134 -0
- data/test/fixtures/vcr_cassettes/newrelic_threshold_values.yml +192 -0
- data/test/heroku_vector/dyno_scaler_test.rb +240 -0
- data/test/heroku_vector/engine/heroku_test.rb +91 -0
- data/test/heroku_vector/sampler_test.rb +61 -0
- data/test/heroku_vector/source/new_relic_test.rb +77 -0
- data/test/heroku_vector/source/sidekiq_test.rb +78 -0
- data/test/test_helper.rb +40 -0
- metadata +313 -0
@@ -0,0 +1,192 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: http://rpm.newrelic.com/accounts.xml
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
Accept:
|
11
|
+
- application/xml
|
12
|
+
X-Api-Key:
|
13
|
+
- api_key
|
14
|
+
Accept-Encoding:
|
15
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
16
|
+
User-Agent:
|
17
|
+
- Ruby
|
18
|
+
response:
|
19
|
+
status:
|
20
|
+
code: 200
|
21
|
+
message: OK
|
22
|
+
headers:
|
23
|
+
Server:
|
24
|
+
- nginx
|
25
|
+
Date:
|
26
|
+
- Sun, 07 Sep 2014 06:32:38 GMT
|
27
|
+
Content-Type:
|
28
|
+
- application/xml; charset=utf-8
|
29
|
+
Transfer-Encoding:
|
30
|
+
- chunked
|
31
|
+
Connection:
|
32
|
+
- keep-alive
|
33
|
+
Status:
|
34
|
+
- 200 OK
|
35
|
+
X-Newrelic-Api-Version:
|
36
|
+
- v1
|
37
|
+
Etag:
|
38
|
+
- '"6421a9efd042eb1f557d443b6010b3b9"'
|
39
|
+
Cache-Control:
|
40
|
+
- max-age=0, private, must-revalidate
|
41
|
+
X-Ua-Compatible:
|
42
|
+
- IE=Edge,chrome=1
|
43
|
+
X-Runtime:
|
44
|
+
- '0.018450'
|
45
|
+
body:
|
46
|
+
encoding: UTF-8
|
47
|
+
string: |
|
48
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
49
|
+
<accounts type="array">
|
50
|
+
<account>
|
51
|
+
<allow-rails-core>false</allow-rails-core>
|
52
|
+
<api-key>api_key</api-key>
|
53
|
+
<data-access-key>8fe689a3c6e5b099a28551733333c2acaeb2a80136c9357</data-access-key>
|
54
|
+
<id type="integer">1</id>
|
55
|
+
<license-key>adb4b49abefe681dce6d228d764082f1b36c9357</license-key>
|
56
|
+
<name>polar-rails-staging Heroku</name>
|
57
|
+
<partner-external-identifier nil="true"></partner-external-identifier>
|
58
|
+
<phone-number nil="true"></phone-number>
|
59
|
+
<event-feed-uri>/account_feeds/8fe689a3c6e5b099a28551733333c2acaeb2a80136c9357/events.rss</event-feed-uri>
|
60
|
+
<primary-admin>
|
61
|
+
<email>cole.jeff.services@gmail.com</email>
|
62
|
+
<first-name nil="true"></first-name>
|
63
|
+
<last-name nil="true"></last-name>
|
64
|
+
<state>active</state>
|
65
|
+
</primary-admin>
|
66
|
+
<subscription>
|
67
|
+
<annual-renewal-on nil="true"></annual-renewal-on>
|
68
|
+
<expires-on nil="true"></expires-on>
|
69
|
+
<number-of-hosts>unlimited</number-of-hosts>
|
70
|
+
<starts-on>2013-11-02</starts-on>
|
71
|
+
<state>free</state>
|
72
|
+
<product-name>Standard</product-name>
|
73
|
+
</subscription>
|
74
|
+
</account>
|
75
|
+
</accounts>
|
76
|
+
http_version:
|
77
|
+
recorded_at: Sun, 07 Sep 2014 06:32:37 GMT
|
78
|
+
- request:
|
79
|
+
method: get
|
80
|
+
uri: http://rpm.newrelic.com/accounts/1/applications.xml
|
81
|
+
body:
|
82
|
+
encoding: US-ASCII
|
83
|
+
string: ''
|
84
|
+
headers:
|
85
|
+
Accept:
|
86
|
+
- application/xml
|
87
|
+
X-Api-Key:
|
88
|
+
- api_key
|
89
|
+
Accept-Encoding:
|
90
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
91
|
+
User-Agent:
|
92
|
+
- Ruby
|
93
|
+
response:
|
94
|
+
status:
|
95
|
+
code: 200
|
96
|
+
message: OK
|
97
|
+
headers:
|
98
|
+
Server:
|
99
|
+
- nginx
|
100
|
+
Date:
|
101
|
+
- Sun, 07 Sep 2014 06:32:39 GMT
|
102
|
+
Content-Type:
|
103
|
+
- application/xml; charset=utf-8
|
104
|
+
Transfer-Encoding:
|
105
|
+
- chunked
|
106
|
+
Connection:
|
107
|
+
- keep-alive
|
108
|
+
Status:
|
109
|
+
- 200 OK
|
110
|
+
X-Newrelic-Api-Version:
|
111
|
+
- v1
|
112
|
+
Etag:
|
113
|
+
- '"a2f87f24fee08dc231c1275662707617"'
|
114
|
+
Cache-Control:
|
115
|
+
- max-age=0, private, must-revalidate
|
116
|
+
X-Ua-Compatible:
|
117
|
+
- IE=Edge,chrome=1
|
118
|
+
X-Runtime:
|
119
|
+
- '0.027413'
|
120
|
+
body:
|
121
|
+
encoding: UTF-8
|
122
|
+
string: |
|
123
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
124
|
+
<applications type="array">
|
125
|
+
<application>
|
126
|
+
<id type="integer">2</id>
|
127
|
+
<name>polar-rails-staging</name>
|
128
|
+
<overview-url>https://rpm.newrelic.com/accounts/1/applications/2</overview-url>
|
129
|
+
<servers-url>https://rpm.newrelic.com/api/v1/accounts/1/applications/2/servers</servers-url>
|
130
|
+
</application>
|
131
|
+
</applications>
|
132
|
+
http_version:
|
133
|
+
recorded_at: Sun, 07 Sep 2014 06:32:38 GMT
|
134
|
+
- request:
|
135
|
+
method: get
|
136
|
+
uri: http://rpm.newrelic.com/accounts/1/applications/2/threshold_values.xml
|
137
|
+
body:
|
138
|
+
encoding: US-ASCII
|
139
|
+
string: ''
|
140
|
+
headers:
|
141
|
+
Accept:
|
142
|
+
- application/xml
|
143
|
+
X-Api-Key:
|
144
|
+
- 68067fd6b945d14883d2b9fdc0e0d85954baab4036c9357
|
145
|
+
Accept-Encoding:
|
146
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
147
|
+
User-Agent:
|
148
|
+
- Ruby
|
149
|
+
response:
|
150
|
+
status:
|
151
|
+
code: 200
|
152
|
+
message: OK
|
153
|
+
headers:
|
154
|
+
Server:
|
155
|
+
- nginx
|
156
|
+
Date:
|
157
|
+
- Sun, 07 Sep 2014 06:32:39 GMT
|
158
|
+
Content-Type:
|
159
|
+
- application/xml; charset=utf-8
|
160
|
+
Transfer-Encoding:
|
161
|
+
- chunked
|
162
|
+
Connection:
|
163
|
+
- keep-alive
|
164
|
+
Status:
|
165
|
+
- 200 OK
|
166
|
+
X-Newrelic-Api-Version:
|
167
|
+
- v1
|
168
|
+
Etag:
|
169
|
+
- '"b3095ce6278aac5a4e88d824c71f1c1e"'
|
170
|
+
Cache-Control:
|
171
|
+
- max-age=0, private, must-revalidate
|
172
|
+
X-Ua-Compatible:
|
173
|
+
- IE=Edge,chrome=1
|
174
|
+
X-Runtime:
|
175
|
+
- '0.033636'
|
176
|
+
body:
|
177
|
+
encoding: UTF-8
|
178
|
+
string: |
|
179
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
180
|
+
<threshold-values type="array">
|
181
|
+
<threshold_value name="Apdex" metric_value="1.0" threshold_value="1" begin_time="2014-09-07 06:27:57" end_time="2014-09-07 06:30:57" formatted_metric_value="1 [0.5]*"/>
|
182
|
+
<threshold_value name="Error Rate" metric_value="0" threshold_value="1" begin_time="2014-09-07 06:27:57" end_time="2014-09-07 06:30:57" formatted_metric_value="0%"/>
|
183
|
+
<threshold_value name="Throughput" metric_value="123" threshold_value="1" begin_time="2014-09-07 06:27:57" end_time="2014-09-07 06:30:57" formatted_metric_value="123 rpm"/>
|
184
|
+
<threshold_value name="Errors" metric_value="0" threshold_value="1" begin_time="2014-09-07 06:27:57" end_time="2014-09-07 06:30:57" formatted_metric_value="0 epm"/>
|
185
|
+
<threshold_value name="Response Time" metric_value="0" threshold_value="1" begin_time="2014-09-07 06:27:57" end_time="2014-09-07 06:30:57" formatted_metric_value="0 ms"/>
|
186
|
+
<threshold_value name="DB" metric_value="0" threshold_value="1" begin_time="2014-09-07 06:27:57" end_time="2014-09-07 06:30:57" formatted_metric_value="0%"/>
|
187
|
+
<threshold_value name="CPU" metric_value="0.38" threshold_value="1" begin_time="2014-09-07 06:27:57" end_time="2014-09-07 06:30:57" formatted_metric_value="0.38%"/>
|
188
|
+
<threshold_value name="Memory" metric_value="1490" threshold_value="1" begin_time="2014-09-07 06:27:57" end_time="2014-09-07 06:30:57" formatted_metric_value="1,490 MB"/>
|
189
|
+
</threshold-values>
|
190
|
+
http_version:
|
191
|
+
recorded_at: Sun, 07 Sep 2014 06:32:38 GMT
|
192
|
+
recorded_with: VCR 2.9.2
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require File.absolute_path File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
describe HerokuVector::DynoScaler do
|
4
|
+
TestSource = Struct.new(:sample)
|
5
|
+
|
6
|
+
let(:dyno_name) { 'web' }
|
7
|
+
let(:mock_heroku) { mock('heroku') }
|
8
|
+
let(:source) { HerokuVector::Source::NewRelic }
|
9
|
+
|
10
|
+
let(:options) do
|
11
|
+
{
|
12
|
+
:source => source,
|
13
|
+
:period => 60,
|
14
|
+
:sample_size => 10,
|
15
|
+
:min_dynos => 1,
|
16
|
+
:max_dynos => 4,
|
17
|
+
:min_value => 3000,
|
18
|
+
:max_value => 5000,
|
19
|
+
:engine => mock_heroku
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#initialize' do
|
24
|
+
it 'should assign values' do
|
25
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options)
|
26
|
+
|
27
|
+
assert_equal dyno_name, scaler.name
|
28
|
+
assert_equal options[:source], scaler.source.class
|
29
|
+
assert_equal options[:period], scaler.period
|
30
|
+
assert_equal options[:sample_size], scaler.sampler.capacity
|
31
|
+
assert_equal options[:min_dynos], scaler.min_dynos
|
32
|
+
assert_equal options[:max_dynos], scaler.max_dynos
|
33
|
+
assert_equal options[:min_value], scaler.min_value
|
34
|
+
assert_equal options[:max_value], scaler.max_value
|
35
|
+
assert_equal mock_heroku, scaler.engine
|
36
|
+
assert scaler.sampler
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should set default values' do
|
40
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, {
|
41
|
+
:min_value => 100,
|
42
|
+
:max_value => 200,
|
43
|
+
:source => source
|
44
|
+
})
|
45
|
+
|
46
|
+
assert_equal 60, scaler.period
|
47
|
+
assert_equal 5, scaler.sampler.capacity
|
48
|
+
assert_equal 2, scaler.min_dynos
|
49
|
+
assert_equal 10, scaler.max_dynos
|
50
|
+
assert_equal 1, scaler.scale_up_by
|
51
|
+
assert_equal 1, scaler.scale_down_by
|
52
|
+
|
53
|
+
assert scaler.sampler
|
54
|
+
assert_equal HerokuVector::Engine::Heroku, scaler.engine.class
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should require min/max values' do
|
58
|
+
assert_raises(RuntimeError) do
|
59
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options.merge(:min_value => nil))
|
60
|
+
end
|
61
|
+
assert_raises(RuntimeError) do
|
62
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options.merge(:max_value => nil))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should instantiate source class instance' do
|
67
|
+
clazz = 'NewRelic'
|
68
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options.merge(:source => clazz) )
|
69
|
+
assert_equal "HerokuVector::Source::#{clazz}", scaler.source.class.name
|
70
|
+
|
71
|
+
assert_raises(RuntimeError) do
|
72
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options.merge(:source => 'invalid_class'))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#run' do
|
78
|
+
let (:scaler) do
|
79
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options)
|
80
|
+
scaler.source = TestSource.new(1)
|
81
|
+
scaler
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should collect a new sample' do
|
85
|
+
scaler.expects(:collect_sample)
|
86
|
+
|
87
|
+
scaler.run
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should not evaluate scale until enough samples' do
|
91
|
+
scaler.stubs(:enough_samples? => false)
|
92
|
+
scaler.expects(:evaluate_and_scale).never
|
93
|
+
|
94
|
+
scaler.run
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should not evaluate scale if scaled recently' do
|
98
|
+
scaler.stubs(:enough_samples? => true)
|
99
|
+
scaler.stubs(:scaling_too_soon? => true)
|
100
|
+
|
101
|
+
scaler.expects(:evaluate_and_scale).never
|
102
|
+
|
103
|
+
scaler.run
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should evaluate_and_scale w/ enough samples' do
|
107
|
+
scaler.stubs(:enough_samples? => true)
|
108
|
+
scaler.stubs(:scaling_too_soon? => false)
|
109
|
+
|
110
|
+
scaler.expects(:evaluate_and_scale)
|
111
|
+
|
112
|
+
scaler.run
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#reset' do
|
117
|
+
let (:scaler) do
|
118
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options)
|
119
|
+
scaler.source = TestSource.new(1)
|
120
|
+
scaler
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should reset sampler' do
|
124
|
+
assert_equal 0, scaler.sampler.size
|
125
|
+
scaler.collect_sample
|
126
|
+
assert_equal 1, scaler.sampler.size
|
127
|
+
|
128
|
+
scaler.reset
|
129
|
+
|
130
|
+
assert_equal 0, scaler.sampler.size
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should reset last_scale_time' do
|
134
|
+
assert_equal nil, scaler.last_scale_time
|
135
|
+
scaler.record_last_scale_event
|
136
|
+
assert scaler.last_scale_time
|
137
|
+
|
138
|
+
scaler.reset
|
139
|
+
|
140
|
+
assert_equal nil, scaler.last_scale_time
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#evaluate_and_scale' do
|
145
|
+
let (:scaler) do
|
146
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options)
|
147
|
+
scaler.source = TestSource.new(1)
|
148
|
+
scaler.reset
|
149
|
+
scaler
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should not scale dynos inside min/max range' do
|
153
|
+
scaler.stubs(:current_size => 1)
|
154
|
+
scaler.source = TestSource.new(scaler.min_value + 1)
|
155
|
+
scaler.collect_sample
|
156
|
+
|
157
|
+
scaler.expects(:scale_dynos).never
|
158
|
+
|
159
|
+
scaler.evaluate_and_scale
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should scale down dynos below min_value' do
|
163
|
+
scaler.stubs(:current_size => 2)
|
164
|
+
scaler.source = TestSource.new(scaler.min_value + 1)
|
165
|
+
scaler.collect_sample
|
166
|
+
|
167
|
+
scaler.expects(:scale_dynos).with(2, 1)
|
168
|
+
|
169
|
+
scaler.evaluate_and_scale
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should scale up dynos above max_value' do
|
173
|
+
scaler.stubs(:current_size => 1)
|
174
|
+
scaler.source = TestSource.new(scaler.max_value + 1)
|
175
|
+
scaler.collect_sample
|
176
|
+
|
177
|
+
scaler.expects(:scale_dynos).with(1, 2)
|
178
|
+
|
179
|
+
scaler.evaluate_and_scale
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe '#scale_dynos' do
|
184
|
+
let (:scaler) do
|
185
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options)
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'should not scale if amount is the same' do
|
189
|
+
mock_heroku.expects(:scale_dynos).never
|
190
|
+
|
191
|
+
scaler.scale_dynos(1, 1)
|
192
|
+
scaler.scale_dynos(2, 2)
|
193
|
+
scaler.scale_dynos(3, 3)
|
194
|
+
scaler.scale_dynos(4, 4)
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'should not scale below min_dynos' do
|
198
|
+
mock_heroku.expects(:scale_dynos).never
|
199
|
+
|
200
|
+
scaler.scale_dynos(1, 0)
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'should not scale above max_dynos' do
|
204
|
+
mock_heroku.expects(:scale_dynos).never
|
205
|
+
|
206
|
+
scaler.scale_dynos(4, 5)
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'should scale within min/max' do
|
210
|
+
mock_heroku.expects(:scale_dynos).with(scaler.name, 2)
|
211
|
+
|
212
|
+
scaler.scale_dynos(1, 2)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe '#scaling_too_soon?' do
|
217
|
+
let (:scaler) do
|
218
|
+
scaler = HerokuVector::DynoScaler.new(dyno_name, options)
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'should not be too soon w/out last_scale_time' do
|
222
|
+
assert_equal nil, scaler.last_scale_time
|
223
|
+
assert_equal false, scaler.scaling_too_soon?
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'should be too soon near last_scale_time' do
|
227
|
+
scaler.last_scale_time = Time.now
|
228
|
+
|
229
|
+
assert_equal true, scaler.scaling_too_soon?
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'should not be too soon after last_scale_time' do
|
233
|
+
threshold = HerokuVector::DynoScaler::MIN_SCALE_TIME_DELTA_SEC
|
234
|
+
scaler.last_scale_time = Time.now - threshold - 1
|
235
|
+
|
236
|
+
assert_equal false, scaler.scaling_too_soon?
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require File.absolute_path File.dirname(__FILE__) + '/../../test_helper'
|
2
|
+
|
3
|
+
describe HerokuVector::Engine::Heroku do
|
4
|
+
let(:heroku_app_name) { 'app_name' }
|
5
|
+
let(:engine) { HerokuVector::Engine::Heroku.new }
|
6
|
+
before { HerokuVector.stubs(:heroku_app_name => heroku_app_name) }
|
7
|
+
|
8
|
+
describe '#initialize' do
|
9
|
+
it 'should set app name from config' do
|
10
|
+
assert_equal heroku_app_name, engine.app
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should instantiate a Heroku API client' do
|
14
|
+
assert engine.heroku
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#get_dyno_types' do
|
19
|
+
it 'should load dyno definitions from Heroku API' do
|
20
|
+
VCR.use_cassette('heroku_dyno_types') do
|
21
|
+
dyno_types = engine.get_dyno_types
|
22
|
+
dynos_by_name = dyno_types.index_by {|dyno| dyno["name"] }
|
23
|
+
assert_equal 1, dynos_by_name['web']['quantity']
|
24
|
+
expected_command = "bin/nginx_wrapper bundle exec rainbows -c ./config/rainbows.rb"
|
25
|
+
assert_equal expected_command, dynos_by_name['web']['command']
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#scale_dynos' do
|
31
|
+
it 'should call Heroku API and issue command' do
|
32
|
+
VCR.use_cassette('heroku_scale_dynos') do
|
33
|
+
engine.scale_dynos('web', 1)
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#get_dynos_by_name' do
|
40
|
+
it 'should index dyno array by name' do
|
41
|
+
dynos = [{ 'name' => 'web', 'quantity' => 1 }]
|
42
|
+
engine.expects(:get_dyno_types => dynos)
|
43
|
+
|
44
|
+
dynos_by_name = engine.get_dynos_by_name
|
45
|
+
assert_equal 1, dynos_by_name['web']['quantity']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#get_dynos_by_name_cached' do
|
50
|
+
it 'should cache calls to get_dynos_by_name' do
|
51
|
+
value = 'dynos'
|
52
|
+
engine.expects(:get_dynos_by_name => value).once
|
53
|
+
|
54
|
+
time = Time.now
|
55
|
+
Timecop.freeze(time) do
|
56
|
+
assert_equal value, engine.get_dynos_by_name_cached
|
57
|
+
assert_equal value, engine.get_dynos_by_name_cached
|
58
|
+
cache_entry = engine.instance_variable_get(:@dynos_by_name)
|
59
|
+
assert cache_entry
|
60
|
+
assert_equal value, cache_entry.data
|
61
|
+
assert_equal time + 60, cache_entry.expires_at
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should get new value when cached value expired' do
|
66
|
+
value = 'dynos'
|
67
|
+
engine.expects(:get_dynos_by_name => value).twice
|
68
|
+
|
69
|
+
time = Time.now
|
70
|
+
Timecop.freeze(time) do
|
71
|
+
assert_equal value, engine.get_dynos_by_name_cached
|
72
|
+
assert_equal value, engine.get_dynos_by_name_cached
|
73
|
+
end
|
74
|
+
Timecop.freeze(time+61) do
|
75
|
+
assert_equal value, engine.get_dynos_by_name_cached
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#count_for_dyno_name' do
|
80
|
+
it 'should lookup dyno count' do
|
81
|
+
dynos = {
|
82
|
+
'web' => { 'name' => 'web', 'quantity' => 1 }
|
83
|
+
}
|
84
|
+
engine.stubs(:get_dynos_by_name_cached => dynos)
|
85
|
+
|
86
|
+
assert_equal 0, engine.count_for_dyno_name('unknown'), 'should return 0 for unknown dyno'
|
87
|
+
assert_equal 1, engine.count_for_dyno_name('web'), 'should return dyno quantity from dynos set'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|