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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 87808cd77975fc82ac067b5c0c82f72f993f6b77
4
- data.tar.gz: 74889c759914d18561ec967cd77edde519bd6d5d
3
+ metadata.gz: c0f2e7b33bd03af9f5b4aa606de11a0a1068b931
4
+ data.tar.gz: 922098a0bf91d9c782c3ac147e94af11f834cf94
5
5
  SHA512:
6
- metadata.gz: 427a52c594b433cc8a506ebe995d56c64b7f1174766c74ce43f476cbf4f848656c2e9c9f6867a8cc31fcadecd92c64a7fcff4db865e7e4cca2c0605e18f6fc0b
7
- data.tar.gz: 6236374797da10131120158b8c2c65f2cff3b4654f3b3ca24a0c85a27f02a6ccb20025babab8c412f6ea82d8457027d600544b9cd857c16fdea2a4636cfb38f8
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
- ## TODO
200
- * ~~Fix Faraday integration to return a Faraday response object~~
201
- * Split stats into it's own repository
202
- * ~~Circuit Breaker should raise an exception by default instead of returning nil~~
203
- * Refactor to use single state variable
204
- * Fix the partition hack
205
- * Integrate with Breakerbox/Hystrix
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
- if cache_key
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
- @opts = opts
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
- response = circuit(request_env).run(run_options(request_env)) do
32
- service_response = @app.call(request_env)
33
- raise RequestFailed unless service_response.success?
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
- response.nil? ? circuit_open_value(request_env, service_response) : response
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
@@ -1,3 +1,3 @@
1
1
  class Circuitbox
2
- VERSION='0.9.0'
2
+ VERSION='0.10.0'
3
3
  end
@@ -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), "sential"
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), :sential
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), :sential
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) { nil }
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 Faraday.default_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
- assert open_circuit_response.status, 503
28
- assert_match open_circuit_response.original_response.body, "Failure!"
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.9.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-16 00:00:00.000000000 Z
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.5
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.