circuitbox 1.0.3 → 2.0.0.pre3
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 +4 -4
- data/README.md +68 -122
- data/lib/circuitbox.rb +12 -56
- data/lib/circuitbox/circuit_breaker.rb +133 -154
- data/lib/circuitbox/circuit_breaker/logger_messages.rb +31 -0
- data/lib/circuitbox/configuration.rb +55 -0
- data/lib/circuitbox/errors/error.rb +1 -2
- data/lib/circuitbox/errors/open_circuit_error.rb +0 -1
- data/lib/circuitbox/errors/service_failure_error.rb +2 -1
- data/lib/circuitbox/excon_middleware.rb +15 -24
- data/lib/circuitbox/faraday_middleware.rb +38 -61
- data/lib/circuitbox/memory_store.rb +84 -0
- data/lib/circuitbox/memory_store/container.rb +28 -0
- data/lib/circuitbox/memory_store/monotonic_time.rb +11 -0
- data/lib/circuitbox/notifier/active_support.rb +19 -0
- data/lib/circuitbox/notifier/null.rb +11 -0
- data/lib/circuitbox/timer/monotonic.rb +17 -0
- data/lib/circuitbox/timer/null.rb +9 -0
- data/lib/circuitbox/timer/simple.rb +13 -0
- data/lib/circuitbox/version.rb +1 -1
- metadata +81 -149
- data/.gitignore +0 -20
- data/.ruby-version +0 -1
- data/.travis.yml +0 -8
- data/Gemfile +0 -6
- data/Rakefile +0 -18
- data/benchmark/circuit_store_benchmark.rb +0 -114
- data/circuitbox.gemspec +0 -38
- data/lib/circuitbox/memcache_store.rb +0 -31
- data/lib/circuitbox/notifier.rb +0 -34
- data/test/circuit_breaker_test.rb +0 -449
- data/test/circuitbox_test.rb +0 -54
- data/test/excon_middleware_test.rb +0 -131
- data/test/faraday_middleware_test.rb +0 -175
- data/test/integration/circuitbox_cross_process_open_test.rb +0 -56
- data/test/integration/faraday_middleware_test.rb +0 -78
- data/test/integration_helper.rb +0 -48
- data/test/notifier_test.rb +0 -21
- data/test/service_failure_error_test.rb +0 -30
- data/test/test_helper.rb +0 -15
data/.gitignore
DELETED
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.1.5
|
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'rake/testtask'
|
2
|
-
require "bundler/gem_version_tasks"
|
3
|
-
|
4
|
-
Rake::TestTask.new do |t|
|
5
|
-
t.libs << 'test'
|
6
|
-
t.test_files = FileList['test/**/*_test.rb']
|
7
|
-
end
|
8
|
-
|
9
|
-
desc "run the circuitbox benchmark scripts"
|
10
|
-
task :benchmark do
|
11
|
-
benchmark_scripts = FileList.new("./benchmark/*_benchmark.rb")
|
12
|
-
benchmark_scripts.each do |script|
|
13
|
-
system "bundle exec ruby #{script}"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
desc "Run tests"
|
18
|
-
task :default => :test
|
@@ -1,114 +0,0 @@
|
|
1
|
-
require 'circuitbox'
|
2
|
-
require 'benchmark'
|
3
|
-
require 'pstore'
|
4
|
-
require 'tempfile'
|
5
|
-
require 'tmpdir'
|
6
|
-
require 'lmdb'
|
7
|
-
require 'pry'
|
8
|
-
|
9
|
-
|
10
|
-
class Circuitbox
|
11
|
-
class CircuitBreaker
|
12
|
-
# silence the circuitbreaker logger
|
13
|
-
DEV_NULL = (RUBY_PLATFORM =~ /mswin|mingw/ ? "NUL" : "/dev/null")
|
14
|
-
def logger
|
15
|
-
@_dev_null_logger ||= Logger.new DEV_NULL
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def service
|
21
|
-
# 10% success rate to make the circuitbreaker flip flop
|
22
|
-
if rand(10) <= 0
|
23
|
-
"success"
|
24
|
-
else
|
25
|
-
raise RuntimeError, "fail"
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def run_flip_flopping circuit
|
30
|
-
circuit.run { service }
|
31
|
-
circuit.try_close_next_time if circuit.open?
|
32
|
-
end
|
33
|
-
|
34
|
-
def without_gc
|
35
|
-
GC.start
|
36
|
-
GC.disable
|
37
|
-
yield
|
38
|
-
GC.enable
|
39
|
-
end
|
40
|
-
|
41
|
-
def benchmark_circuitbox_method_with_reporter method, reporter
|
42
|
-
without_gc { send(method, reporter) }
|
43
|
-
Circuitbox.reset
|
44
|
-
end
|
45
|
-
|
46
|
-
def circuit_with_cache cache
|
47
|
-
Circuitbox.circuit :performance, CIRCUIT_OPTIONS.merge(cache: cache)
|
48
|
-
end
|
49
|
-
|
50
|
-
CIRCUIT_OPTIONS = {
|
51
|
-
exceptions: [RuntimeError],
|
52
|
-
sleep_window: 0,
|
53
|
-
time_window: 1
|
54
|
-
}
|
55
|
-
|
56
|
-
RUNS = 10000
|
57
|
-
|
58
|
-
def circuit_store_memory_one_process reporter
|
59
|
-
circuit = circuit_with_cache Moneta.new(:Memory)
|
60
|
-
|
61
|
-
reporter.report "memory:" do
|
62
|
-
RUNS.times { run_flip_flopping circuit }
|
63
|
-
end
|
64
|
-
|
65
|
-
circuit.circuit_store.close
|
66
|
-
end
|
67
|
-
|
68
|
-
def circuit_store_pstore_one_process reporter
|
69
|
-
Tempfile.create("test_circuit_store_pstore_one_process") do |dbfile|
|
70
|
-
circuit = circuit_with_cache Moneta.new(:PStore, file: dbfile)
|
71
|
-
|
72
|
-
reporter.report "pstore:" do
|
73
|
-
RUNS.times { run_flip_flopping circuit }
|
74
|
-
end
|
75
|
-
|
76
|
-
circuit.circuit_store.close
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def circuit_store_lmdb_one_process reporter
|
81
|
-
Dir.mktmpdir("test_circuit_store_lmdb_one_process") do |dbdir|
|
82
|
-
circuit = circuit_with_cache Moneta.new(:LMDB, dir: dbdir, db: "circuitbox_lmdb")
|
83
|
-
|
84
|
-
reporter.report "lmdb:" do
|
85
|
-
RUNS.times { run_flip_flopping circuit }
|
86
|
-
end
|
87
|
-
|
88
|
-
circuit.circuit_store.close
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def circuit_store_daybreak_one_process reporter
|
93
|
-
Tempfile.create("test_circuit_store_daybreak_one_process") do |dbfile|
|
94
|
-
circuit = circuit_with_cache Moneta.new(:Daybreak, file: dbfile)
|
95
|
-
|
96
|
-
reporter.report "daybreak:" do
|
97
|
-
RUNS.times { run_flip_flopping circuit }
|
98
|
-
end
|
99
|
-
|
100
|
-
circuit.circuit_store.close
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
Benchmark.bm(8) do |x|
|
105
|
-
benchmark_circuitbox_method_with_reporter :circuit_store_memory_one_process, x
|
106
|
-
benchmark_circuitbox_method_with_reporter :circuit_store_lmdb_one_process, x
|
107
|
-
benchmark_circuitbox_method_with_reporter :circuit_store_pstore_one_process, x
|
108
|
-
benchmark_circuitbox_method_with_reporter :circuit_store_daybreak_one_process, x
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
data/circuitbox.gemspec
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'circuitbox/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "circuitbox"
|
8
|
-
spec.version = Circuitbox::VERSION
|
9
|
-
spec.authors = ["Fahim Ferdous"]
|
10
|
-
spec.email = ["fahimfmf@gmail.com"]
|
11
|
-
spec.description = %q{A robust circuit breaker that manages failing external services.}
|
12
|
-
spec.summary = %q{A robust circuit breaker that manages failing external services.}
|
13
|
-
spec.homepage = ""
|
14
|
-
spec.license = "MIT"
|
15
|
-
|
16
|
-
spec.files = `git ls-files`.split($/)
|
17
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = ["lib"]
|
20
|
-
|
21
|
-
spec.add_development_dependency "bundler"
|
22
|
-
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency "rack"
|
24
|
-
spec.add_development_dependency "gimme"
|
25
|
-
spec.add_development_dependency "minitest"
|
26
|
-
spec.add_development_dependency "mocha"
|
27
|
-
spec.add_development_dependency "typhoeus"
|
28
|
-
spec.add_development_dependency "timecop"
|
29
|
-
spec.add_development_dependency "faraday"
|
30
|
-
spec.add_development_dependency "excon"
|
31
|
-
spec.add_development_dependency "logger"
|
32
|
-
spec.add_development_dependency "bundler-gem_version_tasks"
|
33
|
-
spec.add_development_dependency "lmdb"
|
34
|
-
spec.add_development_dependency "daybreak"
|
35
|
-
|
36
|
-
spec.add_dependency "activesupport"
|
37
|
-
spec.add_dependency "moneta"
|
38
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module ActiveSupport
|
2
|
-
module Cache
|
3
|
-
class MemcacheStore
|
4
|
-
def initialize(cache)
|
5
|
-
@cache = cache
|
6
|
-
end
|
7
|
-
|
8
|
-
def read(key, options = {})
|
9
|
-
@cache.get(key, options)
|
10
|
-
rescue Memcached::NotFound
|
11
|
-
nil
|
12
|
-
end
|
13
|
-
|
14
|
-
def increment(key)
|
15
|
-
@cache.incr(key)
|
16
|
-
end
|
17
|
-
|
18
|
-
def write(key, value, options = {})
|
19
|
-
if expires_in = options.delete(:expires_in)
|
20
|
-
options[:expiry] = expires_in.to_i
|
21
|
-
end
|
22
|
-
|
23
|
-
@cache.set(key, value, options)
|
24
|
-
end
|
25
|
-
|
26
|
-
def delete(key)
|
27
|
-
@cache.delete(key)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
data/lib/circuitbox/notifier.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
class Circuitbox
|
2
|
-
class Notifier
|
3
|
-
def initialize(service, partition=nil)
|
4
|
-
@service = service
|
5
|
-
@partition = partition
|
6
|
-
end
|
7
|
-
|
8
|
-
def notify(event)
|
9
|
-
return unless notification_available?
|
10
|
-
ActiveSupport::Notifications.instrument("circuit_#{event}", circuit: circuit_name)
|
11
|
-
end
|
12
|
-
|
13
|
-
def notify_warning(message)
|
14
|
-
return unless notification_available?
|
15
|
-
ActiveSupport::Notifications.instrument("circuit_warning", { circuit: circuit_name, message: message})
|
16
|
-
end
|
17
|
-
|
18
|
-
def metric_gauge(gauge, value)
|
19
|
-
return unless notification_available?
|
20
|
-
ActiveSupport::Notifications.instrument("circuit_gauge", { circuit: circuit_name, gauge: gauge.to_s, value: value })
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
def circuit_name
|
25
|
-
circuit_name = @service.to_s
|
26
|
-
circuit_name += ":#{@partition}" if @partition
|
27
|
-
circuit_name
|
28
|
-
end
|
29
|
-
|
30
|
-
def notification_available?
|
31
|
-
defined? ActiveSupport::Notifications
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,449 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'ostruct'
|
3
|
-
|
4
|
-
class CircuitBreakerTest < Minitest::Test
|
5
|
-
SUCCESSFUL_RESPONSE_STRING = "Success!"
|
6
|
-
RequestFailureError = Timeout::Error
|
7
|
-
class ConnectionError < StandardError; end;
|
8
|
-
class SomeOtherError < StandardError; end;
|
9
|
-
|
10
|
-
def setup
|
11
|
-
Circuitbox::CircuitBreaker.reset
|
12
|
-
end
|
13
|
-
|
14
|
-
describe 'initialize' do
|
15
|
-
it 'force sleep_window to equal time_window if it is too short' do
|
16
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer,
|
17
|
-
:sleep_window => 1,
|
18
|
-
:time_window => 10
|
19
|
-
)
|
20
|
-
assert_equal circuit.option_value(:sleep_window),
|
21
|
-
circuit.option_value(:time_window),
|
22
|
-
'sleep_window has not been corrected properly'
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def test_goes_into_half_open_state_on_sleep
|
27
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
28
|
-
circuit.send(:open!)
|
29
|
-
assert circuit.send(:half_open?)
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
describe 'ratio' do
|
34
|
-
def cb_options
|
35
|
-
{
|
36
|
-
sleep_window: 300,
|
37
|
-
volume_threshold: 5,
|
38
|
-
error_threshold: 33,
|
39
|
-
timeout_seconds: 1
|
40
|
-
}
|
41
|
-
end
|
42
|
-
|
43
|
-
def setup
|
44
|
-
Circuitbox::CircuitBreaker.reset
|
45
|
-
@circuit = Circuitbox::CircuitBreaker.new(:yammer, cb_options)
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
|
-
it 'open the circuit on 100% failure' do
|
50
|
-
run_counter = 0
|
51
|
-
10.times do
|
52
|
-
@circuit.run do
|
53
|
-
run_counter += 1
|
54
|
-
raise RequestFailureError
|
55
|
-
end
|
56
|
-
end
|
57
|
-
assert_equal 6, run_counter, 'the circuit did not open after 6 failures (5 failures + 10%)'
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'keep circuit closed on 0% failure' do
|
61
|
-
run_counter = 0
|
62
|
-
10.times do
|
63
|
-
@circuit.run do
|
64
|
-
run_counter += 1
|
65
|
-
'sucess'
|
66
|
-
end
|
67
|
-
end
|
68
|
-
assert_equal 10, run_counter, 'run block was not executed 10 times'
|
69
|
-
end
|
70
|
-
|
71
|
-
it 'open the circuit even after 1 success' do
|
72
|
-
run_counter = 0
|
73
|
-
5.times do
|
74
|
-
@circuit.run do
|
75
|
-
run_counter += 1
|
76
|
-
raise RequestFailureError
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# one success
|
81
|
-
@circuit.run { 'success'}
|
82
|
-
assert_equal 5, @circuit.failure_count, 'the total count of failures is not 5'
|
83
|
-
|
84
|
-
5.times do
|
85
|
-
@circuit.run do
|
86
|
-
run_counter += 1
|
87
|
-
raise RequestFailureError
|
88
|
-
end
|
89
|
-
end
|
90
|
-
assert_equal 5, run_counter, 'the circuit did not open after 5 failures (5 failures + 10%)'
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'keep circuit closed when failure ratio do not exceed limit' do
|
94
|
-
run_counter = 0
|
95
|
-
7.times do
|
96
|
-
@circuit.run do
|
97
|
-
run_counter += 1
|
98
|
-
'sucess'
|
99
|
-
end
|
100
|
-
end
|
101
|
-
assert_equal 0, @circuit.failure_count, 'some errors were counted'
|
102
|
-
|
103
|
-
3.times do
|
104
|
-
@circuit.run do
|
105
|
-
run_counter += 1
|
106
|
-
raise RequestFailureError
|
107
|
-
end
|
108
|
-
end
|
109
|
-
assert_equal 10, run_counter, 'block was not executed 10 times'
|
110
|
-
assert @circuit.error_rate < 33, 'error_rate pass over 33%'
|
111
|
-
end
|
112
|
-
|
113
|
-
it 'circuit open when failure ratio exceed limit' do
|
114
|
-
run_counter = 0
|
115
|
-
10.times do
|
116
|
-
@circuit.run do
|
117
|
-
run_counter += 1
|
118
|
-
'sucess'
|
119
|
-
end
|
120
|
-
end
|
121
|
-
assert_equal 0, @circuit.failure_count, 'some errors were counted'
|
122
|
-
|
123
|
-
10.times do
|
124
|
-
@circuit.run do
|
125
|
-
run_counter += 1
|
126
|
-
raise RequestFailureError
|
127
|
-
end
|
128
|
-
end
|
129
|
-
# 5 failure on 15 run is 33%
|
130
|
-
assert_equal 15, run_counter, 'block was not executed 10 times'
|
131
|
-
assert @circuit.error_rate >= 33, 'error_rate pass over 33%'
|
132
|
-
end
|
133
|
-
|
134
|
-
end
|
135
|
-
|
136
|
-
describe 'exceptions' do
|
137
|
-
before do
|
138
|
-
Circuitbox::CircuitBreaker.reset
|
139
|
-
@circuit = Circuitbox::CircuitBreaker.new(:yammer, exceptions: [SomeOtherError])
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'raises exception when circuit is open' do
|
143
|
-
@circuit.stubs(open_flag?: true)
|
144
|
-
-> { @circuit.run! {} }.must_raise Circuitbox::OpenCircuitError
|
145
|
-
end
|
146
|
-
|
147
|
-
it 'raises exception when service fails' do
|
148
|
-
err = -> { @circuit.run! { raise SomeOtherError } }.must_raise Circuitbox::ServiceFailureError
|
149
|
-
err.original.must_be_instance_of SomeOtherError
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
describe 'closing the circuit after sleep' do
|
154
|
-
def cb_options
|
155
|
-
{
|
156
|
-
sleep_window: 1,
|
157
|
-
time_window: 2,
|
158
|
-
volume_threshold: 5,
|
159
|
-
error_threshold: 5,
|
160
|
-
timeout_seconds: 1
|
161
|
-
}
|
162
|
-
end
|
163
|
-
|
164
|
-
def setup
|
165
|
-
@circuit = Circuitbox::CircuitBreaker.new(:yammer, cb_options)
|
166
|
-
end
|
167
|
-
|
168
|
-
it 'close the circuit after sleeping time' do
|
169
|
-
# lets open the circuit
|
170
|
-
(cb_options[:error_threshold] + 1).times { @circuit.run { raise RequestFailureError } }
|
171
|
-
run_count = 0
|
172
|
-
@circuit.run { run_count += 1 }
|
173
|
-
assert_equal 0, run_count, 'circuit is not open'
|
174
|
-
# it is + 2 on purpose, because + 1 is flaky here
|
175
|
-
sleep cb_options[:sleep_window] + 2
|
176
|
-
|
177
|
-
@circuit.run { run_count += 1 }
|
178
|
-
assert_equal 1, run_count, 'circuit is not closed'
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
describe "when in half open state" do
|
183
|
-
before do
|
184
|
-
Circuitbox::CircuitBreaker.reset
|
185
|
-
@circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
186
|
-
end
|
187
|
-
|
188
|
-
it "opens circuit on next failed request" do
|
189
|
-
@circuit.stubs(half_open?: true)
|
190
|
-
@circuit.expects(:open!)
|
191
|
-
@circuit.run { raise RequestFailureError }
|
192
|
-
end
|
193
|
-
|
194
|
-
it "closes circuit on successful request" do
|
195
|
-
@circuit.send(:half_open!)
|
196
|
-
@circuit.run { 'success' }
|
197
|
-
assert !@circuit.send(:half_open?)
|
198
|
-
assert !@circuit.send(:open?)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def test_should_use_timeout_class_if_exceptions_are_not_defined
|
203
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer, timeout_seconds: 45)
|
204
|
-
circuit.expects(:timeout).with(45).once
|
205
|
-
emulate_circuit_run(circuit, :success, StandardError)
|
206
|
-
end
|
207
|
-
|
208
|
-
def test_should_not_use_timeout_class_if_custom_exceptions_are_defined
|
209
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer, exceptions: [ConnectionError])
|
210
|
-
circuit.expects(:timeout).never
|
211
|
-
emulate_circuit_run(circuit, :success, StandardError)
|
212
|
-
end
|
213
|
-
|
214
|
-
def test_should_return_response_if_it_doesnt_timeout
|
215
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
216
|
-
response = emulate_circuit_run(circuit, :success, SUCCESSFUL_RESPONSE_STRING)
|
217
|
-
assert_equal SUCCESSFUL_RESPONSE_STRING, response
|
218
|
-
end
|
219
|
-
|
220
|
-
def test_timeout_seconds_run_options_overrides_circuit_options
|
221
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer, timeout_seconds: 60)
|
222
|
-
circuit.expects(:timeout).with(30).once
|
223
|
-
circuit.run(timeout_seconds: 30) { true }
|
224
|
-
end
|
225
|
-
|
226
|
-
def test_catches_connection_error_failures_if_defined
|
227
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [ConnectionError])
|
228
|
-
response = emulate_circuit_run(circuit, :failure, ConnectionError)
|
229
|
-
assert_equal nil, response
|
230
|
-
end
|
231
|
-
|
232
|
-
def test_doesnt_catch_out_of_scope_exceptions
|
233
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [ConnectionError, RequestFailureError])
|
234
|
-
|
235
|
-
assert_raises SomeOtherError do
|
236
|
-
emulate_circuit_run(circuit, :failure, SomeOtherError)
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
def test_records_response_failure
|
241
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer, :exceptions => [RequestFailureError])
|
242
|
-
circuit.expects(:log_event).with(:failure)
|
243
|
-
emulate_circuit_run(circuit, :failure, RequestFailureError)
|
244
|
-
end
|
245
|
-
|
246
|
-
def test_records_response_success
|
247
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
248
|
-
circuit.expects(:log_event).with(:success)
|
249
|
-
emulate_circuit_run(circuit, :success, SUCCESSFUL_RESPONSE_STRING)
|
250
|
-
end
|
251
|
-
|
252
|
-
def test_does_not_send_request_if_circuit_is_open
|
253
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
254
|
-
circuit.stubs(:open? => true)
|
255
|
-
circuit.expects(:yield).never
|
256
|
-
response = emulate_circuit_run(circuit, :failure, RequestFailureError)
|
257
|
-
assert_equal nil, response
|
258
|
-
end
|
259
|
-
|
260
|
-
def test_returns_nil_response_on_failed_request
|
261
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
262
|
-
response = emulate_circuit_run(circuit, :failure, RequestFailureError)
|
263
|
-
assert_equal nil, response
|
264
|
-
end
|
265
|
-
|
266
|
-
def test_puts_circuit_to_sleep_once_opened
|
267
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
268
|
-
circuit.stubs(:open? => true)
|
269
|
-
|
270
|
-
assert !circuit.send(:open_flag?)
|
271
|
-
emulate_circuit_run(circuit, :failure, RequestFailureError)
|
272
|
-
assert circuit.send(:open_flag?)
|
273
|
-
|
274
|
-
circuit.expects(:open!).never
|
275
|
-
emulate_circuit_run(circuit, :failure, RequestFailureError)
|
276
|
-
end
|
277
|
-
|
278
|
-
def test_open_is_true_if_open_flag
|
279
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
280
|
-
circuit.stubs(:open_flag? => true)
|
281
|
-
assert circuit.open?
|
282
|
-
end
|
283
|
-
|
284
|
-
def test_open_checks_if_volume_threshold_has_passed
|
285
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
286
|
-
circuit.stubs(:open_flag? => false)
|
287
|
-
|
288
|
-
circuit.expects(:passed_volume_threshold?).once
|
289
|
-
circuit.open?
|
290
|
-
end
|
291
|
-
|
292
|
-
def test_open_checks_error_rate_threshold
|
293
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
294
|
-
circuit.stubs(:open_flag? => false,
|
295
|
-
:passed_volume_threshold? => true)
|
296
|
-
|
297
|
-
circuit.expects(:passed_rate_threshold?).once
|
298
|
-
circuit.open?
|
299
|
-
end
|
300
|
-
|
301
|
-
def test_open_is_false_if_awake_and_under_rate_threshold
|
302
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
303
|
-
circuit.stubs(:open_flag? => false,
|
304
|
-
:passed_volume_threshold? => false,
|
305
|
-
:passed_rate_threshold => false)
|
306
|
-
|
307
|
-
assert !circuit.open?
|
308
|
-
end
|
309
|
-
|
310
|
-
def test_error_rate_threshold_calculation
|
311
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
312
|
-
circuit.stubs(:failure_count => 3, :success_count => 2)
|
313
|
-
assert circuit.send(:passed_rate_threshold?)
|
314
|
-
|
315
|
-
circuit.stubs(:failure_count => 2, :success_count => 3)
|
316
|
-
assert !circuit.send(:passed_rate_threshold?)
|
317
|
-
end
|
318
|
-
|
319
|
-
def test_logs_and_retrieves_success_events
|
320
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
321
|
-
5.times { circuit.send(:log_event, :success) }
|
322
|
-
assert_equal 5, circuit.send(:success_count)
|
323
|
-
end
|
324
|
-
|
325
|
-
def test_logs_and_retrieves_failure_events
|
326
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
327
|
-
5.times { circuit.send(:log_event, :failure) }
|
328
|
-
assert_equal 5, circuit.send(:failure_count)
|
329
|
-
end
|
330
|
-
|
331
|
-
def test_logs_events_by_minute
|
332
|
-
circuit = Circuitbox::CircuitBreaker.new(:yammer)
|
333
|
-
|
334
|
-
Timecop.travel(Time.now.change(sec: 5))
|
335
|
-
4.times { circuit.send(:log_event, :success) }
|
336
|
-
assert_equal 4, circuit.send(:success_count)
|
337
|
-
|
338
|
-
Timecop.travel(1.minute.from_now)
|
339
|
-
7.times { circuit.send(:log_event, :success) }
|
340
|
-
assert_equal 7, circuit.send(:success_count)
|
341
|
-
|
342
|
-
Timecop.travel(30.seconds.from_now)
|
343
|
-
circuit.send(:log_event, :success)
|
344
|
-
assert_equal 8, circuit.send(:success_count)
|
345
|
-
|
346
|
-
Timecop.travel(50.seconds.from_now)
|
347
|
-
assert_equal 0, circuit.send(:success_count)
|
348
|
-
end
|
349
|
-
|
350
|
-
describe 'notifications' do
|
351
|
-
|
352
|
-
def setup
|
353
|
-
Circuitbox::CircuitBreaker.reset
|
354
|
-
end
|
355
|
-
|
356
|
-
def circuit
|
357
|
-
Circuitbox::CircuitBreaker.new(:yammer, :notifier_class => @notifier)
|
358
|
-
end
|
359
|
-
|
360
|
-
|
361
|
-
it 'notifies on open circuit' do
|
362
|
-
@notifier = gimme_notifier
|
363
|
-
c = circuit
|
364
|
-
10.times { c.run { raise RequestFailureError }}
|
365
|
-
assert @notifier.notified?, 'no notification sent'
|
366
|
-
end
|
367
|
-
|
368
|
-
it 'notifies on close circuit' do
|
369
|
-
@notifier = gimme_notifier
|
370
|
-
c = circuit
|
371
|
-
5.times { c.run { raise RequestFailureError }}
|
372
|
-
clear_notified!
|
373
|
-
10.times { c.run { 'success' }}
|
374
|
-
assert @notifier.notified?, 'no notification sent'
|
375
|
-
end
|
376
|
-
|
377
|
-
it 'notifies warning if sleep_window is shorter than time_window' do
|
378
|
-
@notifier = gimme_notifier
|
379
|
-
Circuitbox::CircuitBreaker.new(:yammer,
|
380
|
-
:notifier_class => @notifier,
|
381
|
-
:sleep_window => 1,
|
382
|
-
:time_window => 10
|
383
|
-
)
|
384
|
-
assert @notifier.notified?, 'no notification sent'
|
385
|
-
end
|
386
|
-
|
387
|
-
it 'DO NOT notifies warning if sleep_window is longer than time_window' do
|
388
|
-
@notifier = gimme_notifier
|
389
|
-
Circuitbox::CircuitBreaker.new(:yammer,
|
390
|
-
:notifier_class => @notifier,
|
391
|
-
:sleep_window => 11,
|
392
|
-
:time_window => 10
|
393
|
-
)
|
394
|
-
assert_equal false, @notifier.notified?, 'no notification sent'
|
395
|
-
end
|
396
|
-
|
397
|
-
|
398
|
-
it 'notifies error_rate on error_rate calculation' do
|
399
|
-
@notifier = gimme_notifier(metric: :error_rate, metric_value: 0.0)
|
400
|
-
10.times { circuit.run {'success' }}
|
401
|
-
assert @notifier.notified?, 'no notification sent'
|
402
|
-
end
|
403
|
-
|
404
|
-
it 'notifies failure_count on error_rate calculation' do
|
405
|
-
@notifier = gimme_notifier(metric: :failure_count, metric_value: 1)
|
406
|
-
10.times { circuit.run { raise RequestFailureError }}
|
407
|
-
assert @notifier.notified?, 'no notification sent'
|
408
|
-
end
|
409
|
-
|
410
|
-
it 'notifies success_count on error_rate calculation' do
|
411
|
-
@notifier = gimme_notifier(metric: :success_count, metric_value: 6)
|
412
|
-
10.times { circuit.run { 'success' }}
|
413
|
-
assert @notifier.notified?, 'no notification sent'
|
414
|
-
end
|
415
|
-
|
416
|
-
def clear_notified!
|
417
|
-
@notified = false
|
418
|
-
end
|
419
|
-
|
420
|
-
def gimme_notifier(opts={})
|
421
|
-
clear_notified!
|
422
|
-
metric = opts.fetch(:metric,:error_rate)
|
423
|
-
metric_value = opts.fetch(:metric_value, 0.0)
|
424
|
-
warning_msg = opts.fetch(:warning_msg, '')
|
425
|
-
fake_notifier = gimme
|
426
|
-
give(fake_notifier).notify(:open) { @notified=true }
|
427
|
-
give(fake_notifier).notify(:close) { @notified=true }
|
428
|
-
give(fake_notifier).notify_warning(Gimme::Matchers::Anything.new) { @notified = true }
|
429
|
-
give(fake_notifier).metric_gauge(metric, metric_value) { @notified=true }
|
430
|
-
fake_notifier_class = gimme
|
431
|
-
give(fake_notifier_class).new(:yammer,nil) { fake_notifier }
|
432
|
-
give(fake_notifier_class).notified? { @notified }
|
433
|
-
fake_notifier_class
|
434
|
-
end
|
435
|
-
end
|
436
|
-
|
437
|
-
def emulate_circuit_run(circuit, response_type, response_value)
|
438
|
-
circuit.run do
|
439
|
-
case response_type
|
440
|
-
when :failure
|
441
|
-
raise response_value
|
442
|
-
when :success
|
443
|
-
response_value
|
444
|
-
end
|
445
|
-
end
|
446
|
-
rescue RequestFailureError
|
447
|
-
nil
|
448
|
-
end
|
449
|
-
end
|