heroku-vector 0.0.2
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 +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
|