circuitbox 0.9.0 → 0.10.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.
- 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.
|