circuitbox 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +21 -7
- data/circuitbox.gemspec +1 -0
- data/lib/circuitbox/circuit_breaker.rb +1 -15
- data/lib/circuitbox/faraday_middleware.rb +19 -7
- data/lib/circuitbox/version.rb +1 -1
- data/test/faraday_middleware_test.rb +35 -8
- data/test/integration/faraday_middleware_test.rb +35 -3
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0f2e7b33bd03af9f5b4aa606de11a0a1068b931
|
4
|
+
data.tar.gz: 922098a0bf91d9c782c3ac147e94af11f834cf94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1fefde3c0c5af41966ff72eee0c71d3c0e5478306b3f744b95eed5ea4e5daeb651ecaf0cf0f7a92eb354571b0af3628c48881e3b29b0f291b8c1a8f1da57adb
|
7
|
+
data.tar.gz: 34add156e9af7dd00a3a12be2c6dde1d05e7f8c5ab5bfcb7006022351ddcbccc57586b340bd6d4b8323bb71cfc02022a49e5fef0bf8afbc88df0288edd93822b
|
data/README.md
CHANGED
@@ -196,13 +196,27 @@ conn.get("/api", circuit_breaker_run_options: {})
|
|
196
196
|
c.use Circuitbox::FaradayMiddleware, circuit_breaker_options: {}
|
197
197
|
```
|
198
198
|
|
199
|
-
|
200
|
-
*
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
199
|
+
<<<<<<< HEAD
|
200
|
+
* `open_circuit` lambda determining what response is considered a failure,
|
201
|
+
counting towards the opening of the circuit
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
c.use Circuitbox::FaradayMiddleware, open_circuit: lambda { |response| response.status >= 500 }
|
205
|
+
```
|
206
|
+
## CHANGELOG
|
207
|
+
<<<<<<< HEAD
|
208
|
+
### version next
|
209
|
+
|
210
|
+
### v0.10
|
211
|
+
- configuration option for faraday middleware for what should be considered to open the circuit [enrico-scalavio](https://github.com/enrico-scalavino)
|
212
|
+
- fix for issue 16, support of in_parallel requests in faraday middlware which were opening the circuit.
|
213
|
+
- deprecate the __run_option__ `:storage_key`
|
214
|
+
|
215
|
+
### v0.9
|
216
|
+
- add `run!` method to raise exception on circuit open and service
|
217
|
+
|
218
|
+
### v0.8
|
219
|
+
- Everything prior to keeping the change log
|
206
220
|
|
207
221
|
## Installation
|
208
222
|
|
data/circuitbox.gemspec
CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "gimme"
|
27
27
|
spec.add_development_dependency "minitest"
|
28
28
|
spec.add_development_dependency "mocha"
|
29
|
+
spec.add_development_dependency "typhoeus"
|
29
30
|
spec.add_development_dependency "timecop"
|
30
31
|
spec.add_development_dependency "faraday"
|
31
32
|
spec.add_development_dependency "logger"
|
@@ -43,7 +43,6 @@ class Circuitbox
|
|
43
43
|
|
44
44
|
def run!(run_options = {})
|
45
45
|
@partition = run_options.delete(:partition) # sorry for this hack.
|
46
|
-
cache_key = run_options.delete(:storage_key)
|
47
46
|
|
48
47
|
if open?
|
49
48
|
logger.debug "[CIRCUIT] open: skipping #{service}"
|
@@ -62,17 +61,12 @@ class Circuitbox
|
|
62
61
|
end
|
63
62
|
|
64
63
|
logger.debug "[CIRCUIT] closed: #{service} querie success"
|
65
|
-
cache_response(cache_key, response) if cache_key
|
66
64
|
success!
|
67
65
|
rescue *exceptions => exception
|
68
66
|
logger.debug "[CIRCUIT] closed: detected #{service} failure"
|
69
67
|
failure!
|
70
68
|
open! if half_open?
|
71
|
-
|
72
|
-
response = get_cached_response(cache_key)
|
73
|
-
else
|
74
|
-
raise Circuitbox::ServiceFailureError.new(service, exception)
|
75
|
-
end
|
69
|
+
raise Circuitbox::ServiceFailureError.new(service, exception)
|
76
70
|
end
|
77
71
|
end
|
78
72
|
|
@@ -252,14 +246,6 @@ class Circuitbox
|
|
252
246
|
Digest::SHA1.hexdigest(storage_key(:cache, args.inspect.to_s))
|
253
247
|
end
|
254
248
|
|
255
|
-
def cache_response(args, response)
|
256
|
-
cache.write(response_key(args), response)
|
257
|
-
end
|
258
|
-
|
259
|
-
def get_cached_response(args)
|
260
|
-
cache.read(response_key(args))
|
261
|
-
end
|
262
|
-
|
263
249
|
def stat_storage_key(event, options = {})
|
264
250
|
storage_key(:stats, align_time_on_minute, event, options)
|
265
251
|
end
|
@@ -22,19 +22,22 @@ class Circuitbox
|
|
22
22
|
|
23
23
|
def initialize(app, opts = {})
|
24
24
|
@app = app
|
25
|
-
|
25
|
+
default_options = { open_circuit: lambda { |response| !response.success? } }
|
26
|
+
@opts = default_options.merge(opts)
|
26
27
|
super(app)
|
27
28
|
end
|
28
29
|
|
29
30
|
def call(request_env)
|
30
31
|
service_response = nil
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
service_response
|
32
|
+
check_circuit_open!(request_env)
|
33
|
+
circuit(request_env).run!(run_options(request_env)) do
|
34
|
+
@app.call(request_env).on_complete do |env|
|
35
|
+
service_response = Faraday::Response.new(env)
|
36
|
+
raise RequestFailed if open_circuit?(service_response)
|
37
|
+
end
|
35
38
|
end
|
36
|
-
|
37
|
-
|
39
|
+
rescue Circuitbox::Error
|
40
|
+
circuit_open_value(request_env, service_response)
|
38
41
|
end
|
39
42
|
|
40
43
|
def exceptions
|
@@ -47,6 +50,11 @@ class Circuitbox
|
|
47
50
|
|
48
51
|
private
|
49
52
|
|
53
|
+
def check_circuit_open!(env)
|
54
|
+
# raises if the circuit is open
|
55
|
+
circuit(env).run!(run_options(env)) { :noop }
|
56
|
+
end
|
57
|
+
|
50
58
|
def run_options(env)
|
51
59
|
env[:circuit_breaker_run_options] || {}
|
52
60
|
end
|
@@ -75,6 +83,10 @@ class Circuitbox
|
|
75
83
|
end
|
76
84
|
end
|
77
85
|
|
86
|
+
def open_circuit?(response)
|
87
|
+
opts[:open_circuit].call(response)
|
88
|
+
end
|
89
|
+
|
78
90
|
def circuitbox
|
79
91
|
@circuitbox ||= opts.fetch(:circuitbox, Circuitbox)
|
80
92
|
end
|
data/lib/circuitbox/version.rb
CHANGED
@@ -7,13 +7,14 @@ class Circuitbox
|
|
7
7
|
class FaradayMiddlewareTest < Minitest::Test
|
8
8
|
|
9
9
|
attr_reader :app
|
10
|
+
|
10
11
|
def setup
|
11
12
|
@app = gimme
|
12
13
|
end
|
13
14
|
|
14
15
|
def test_default_identifier
|
15
16
|
env = { url: "sential" }
|
16
|
-
assert_equal FaradayMiddleware.new(app).identifier.call(env)
|
17
|
+
assert_equal "sential", FaradayMiddleware.new(app).identifier.call(env)
|
17
18
|
end
|
18
19
|
|
19
20
|
def test_overwrite_identifier
|
@@ -25,19 +26,21 @@ class Circuitbox
|
|
25
26
|
stub_circuitbox
|
26
27
|
env = { url: "url" }
|
27
28
|
give(circuitbox).circuit("url", anything) { circuit }
|
29
|
+
give(circuit).run!(anything) { raise Circuitbox::Error }
|
28
30
|
default_value_generator = lambda { |_| :sential }
|
29
31
|
middleware = FaradayMiddleware.new(app,
|
30
32
|
circuitbox: circuitbox,
|
31
33
|
default_value: default_value_generator)
|
32
|
-
assert_equal middleware.call(env)
|
34
|
+
assert_equal :sential, middleware.call(env)
|
33
35
|
end
|
34
36
|
|
35
37
|
def test_overwrite_default_value_generator_static_value
|
36
38
|
stub_circuitbox
|
37
39
|
env = { url: "url" }
|
38
40
|
give(circuitbox).circuit("url", anything) { circuit }
|
41
|
+
give(circuit).run!(anything) { raise Circuitbox::Error }
|
39
42
|
middleware = FaradayMiddleware.new(app, circuitbox: circuitbox, default_value: :sential)
|
40
|
-
assert_equal middleware.call(env)
|
43
|
+
assert_equal :sential, middleware.call(env)
|
41
44
|
end
|
42
45
|
|
43
46
|
def test_default_exceptions
|
@@ -46,6 +49,29 @@ class Circuitbox
|
|
46
49
|
assert_includes middleware.exceptions, FaradayMiddleware::RequestFailed
|
47
50
|
end
|
48
51
|
|
52
|
+
def test_overridde_success_response
|
53
|
+
env = { url: "url" }
|
54
|
+
app = gimme
|
55
|
+
give(app).call(anything) { Faraday::Response.new(status: 400) }
|
56
|
+
error_response = lambda { |response| response.status >= 500 }
|
57
|
+
response = FaradayMiddleware.new(app, open_circuit: error_response).call(env)
|
58
|
+
assert_kind_of Faraday::Response, response
|
59
|
+
assert_equal response.status, 400
|
60
|
+
assert response.finished?
|
61
|
+
refute response.success?
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_default_success_response
|
65
|
+
env = { url: "url" }
|
66
|
+
app = gimme
|
67
|
+
give(app).call(anything) { Faraday::Response.new(status: 400) }
|
68
|
+
response = FaradayMiddleware.new(app).call(env)
|
69
|
+
assert_kind_of Faraday::Response, response
|
70
|
+
assert_equal response.status, 503
|
71
|
+
assert response.finished?
|
72
|
+
refute response.success?
|
73
|
+
end
|
74
|
+
|
49
75
|
def test_overwrite_exceptions
|
50
76
|
middleware = FaradayMiddleware.new(app, exceptions: [SentialException])
|
51
77
|
assert_includes middleware.exceptions, SentialException
|
@@ -53,12 +79,12 @@ class Circuitbox
|
|
53
79
|
|
54
80
|
def test_pass_circuit_breaker_run_options
|
55
81
|
stub_circuitbox
|
56
|
-
give(circuit).run(:sential)
|
82
|
+
give(circuit).run!(:sential)
|
57
83
|
give(circuitbox).circuit("url", anything) { circuit }
|
58
84
|
env = { url: "url", circuit_breaker_run_options: :sential }
|
59
85
|
middleware = FaradayMiddleware.new(app, circuitbox: circuitbox)
|
60
86
|
middleware.call(env)
|
61
|
-
verify(circuit).run(:sential)
|
87
|
+
verify(circuit, 2.times).run!(:sential) # one to check open, one to execute command
|
62
88
|
end
|
63
89
|
|
64
90
|
def test_pass_circuit_breaker_options
|
@@ -74,13 +100,14 @@ class Circuitbox
|
|
74
100
|
middleware = FaradayMiddleware.new(app, options)
|
75
101
|
middleware.call(env)
|
76
102
|
|
77
|
-
verify(circuitbox).circuit("url", expected_circuit_breaker_options)
|
103
|
+
verify(circuitbox, 2.times).circuit("url", expected_circuit_breaker_options)
|
78
104
|
end
|
79
105
|
|
80
106
|
def test_overwrite_circuitbreaker_default_value
|
81
107
|
stub_circuitbox
|
82
108
|
env = { url: "url", circuit_breaker_default_value: :sential }
|
83
109
|
give(circuitbox).circuit("url", anything) { circuit }
|
110
|
+
give(circuit).run!(anything) { raise Circuitbox::Error }
|
84
111
|
middleware = FaradayMiddleware.new(app, circuitbox: circuitbox)
|
85
112
|
assert_equal middleware.call(env), :sential
|
86
113
|
end
|
@@ -88,7 +115,7 @@ class Circuitbox
|
|
88
115
|
def test_return_value_closed_circuit
|
89
116
|
stub_circuitbox
|
90
117
|
env = { url: "url" }
|
91
|
-
give(circuit).run(anything) { :sential }
|
118
|
+
give(circuit).run!(anything) { :sential }
|
92
119
|
give(circuitbox).circuit("url", anything) { circuit }
|
93
120
|
middleware = FaradayMiddleware.new(app, circuitbox: circuitbox)
|
94
121
|
assert_equal middleware.call(env), :sential
|
@@ -97,7 +124,7 @@ class Circuitbox
|
|
97
124
|
def test_return_null_response_for_open_circuit
|
98
125
|
stub_circuitbox
|
99
126
|
env = { url: "url" }
|
100
|
-
give(circuit).run(anything) {
|
127
|
+
give(circuit).run!(anything) { raise Circuitbox::Error }
|
101
128
|
give(circuitbox).circuit("url", anything) { circuit }
|
102
129
|
response = FaradayMiddleware.new(app, circuitbox: circuitbox).call(env)
|
103
130
|
assert_kind_of Faraday::Response, response
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "integration_helper"
|
2
|
+
require "typhoeus/adapters/faraday"
|
2
3
|
|
3
4
|
class Circuitbox
|
4
5
|
class FaradayMiddlewareTest < Minitest::Test
|
@@ -6,7 +7,7 @@ class Circuitbox
|
|
6
7
|
def setup
|
7
8
|
@connection = Faraday.new do |c|
|
8
9
|
c.use FaradayMiddleware
|
9
|
-
c.adapter
|
10
|
+
c.adapter :typhoeus # support in_parallel
|
10
11
|
end
|
11
12
|
@success_url = "http://localhost:4711"
|
12
13
|
@failure_url = "http://localhost:4712"
|
@@ -21,16 +22,47 @@ class Circuitbox
|
|
21
22
|
Circuitbox.reset
|
22
23
|
end
|
23
24
|
|
25
|
+
def test_failure_circuit_response
|
26
|
+
failure_response = @connection.get(@failure_url)
|
27
|
+
assert_equal failure_response.status, 503
|
28
|
+
assert_match failure_response.original_response.body, "Failure!"
|
29
|
+
end
|
30
|
+
|
24
31
|
def test_open_circuit_response
|
25
32
|
10.times { @connection.get(@failure_url) } # make the CircuitBreaker open
|
33
|
+
|
26
34
|
open_circuit_response = @connection.get(@failure_url)
|
27
|
-
|
28
|
-
|
35
|
+
assert_equal open_circuit_response.status, 503
|
36
|
+
assert open_circuit_response.original_response.nil?
|
29
37
|
end
|
30
38
|
|
31
39
|
def test_closed_circuit_response
|
32
40
|
result = @connection.get(@success_url)
|
33
41
|
assert result.success?
|
34
42
|
end
|
43
|
+
|
44
|
+
def test_parallel_requests_closed_circuit_response
|
45
|
+
response_1, response_2 = nil
|
46
|
+
@connection.in_parallel do
|
47
|
+
response_1 = @connection.get(@success_url)
|
48
|
+
response_2 = @connection.get(@success_url)
|
49
|
+
end
|
50
|
+
|
51
|
+
assert response_1.success?
|
52
|
+
assert response_2.success?
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_parallel_requests_open_circuit_response
|
56
|
+
10.times { @connection.get(@failure_url) } # make the CircuitBreaker open
|
57
|
+
response_1, response_2 = nil
|
58
|
+
@connection.in_parallel do
|
59
|
+
response_1 = @connection.get(@failure_url)
|
60
|
+
response_2 = @connection.get(@failure_url)
|
61
|
+
end
|
62
|
+
|
63
|
+
assert_equal response_1.status, 503
|
64
|
+
assert_equal response_2.status, 503
|
65
|
+
end
|
66
|
+
|
35
67
|
end
|
36
68
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: circuitbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fahim Ferdous
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: typhoeus
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
140
|
name: timecop
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -245,7 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
245
259
|
version: '0'
|
246
260
|
requirements: []
|
247
261
|
rubyforge_project:
|
248
|
-
rubygems_version: 2.4.
|
262
|
+
rubygems_version: 2.4.3
|
249
263
|
signing_key:
|
250
264
|
specification_version: 4
|
251
265
|
summary: A robust circuit breaker that manages failing external services.
|