routemaster-drain 3.1.0 → 3.2.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
- SHA1:
3
- metadata.gz: eb1cccd206b6825ce76f748a269bfbc261ff4328
4
- data.tar.gz: a05de95ccc0c4643015f69aeeb7bbf02c4905d4f
2
+ SHA256:
3
+ metadata.gz: 1ddda71d2b877bc368adcfb3d6f97d6c79916b8b2d72db9036d4bdf34ae7f5d8
4
+ data.tar.gz: 2c3371a3ed8b1df43f743bbc3d9e715fc8ea63ea796042b922f564c13da45467
5
5
  SHA512:
6
- metadata.gz: cb053523998b5303d8c47d52963c27fac7a87181f28a33ed0a84e8739103c8031bbb3955567440a229eb0691b8878da257c585d5bcd05ac1ae5d1ba5a6028c39
7
- data.tar.gz: db39fcb620c32576258a6f94ebc70eaed859cd3b5ea61f4ac899fec68df30ed7f9170cb2535b035a2c40de6425a93cd2b87b99f0e3096e6381fafacc3a3be0cb
6
+ metadata.gz: a4034317d864f77c4b111f0837673fc3bc9b7f1cb3f93fa79aeff4170b2d80dcf9f6a3cc58072a5a6819791013612a04c59a34baee67b2380ac0a36dd66a8c52
7
+ data.tar.gz: 51b7c394000cbe0efe657f53ea00b7d4d99063675796a78e8ee3f72e8708e18256311c93792bcd98875a022ac80bc29c20f6eaa034ae178ad6f6ef3196093a38
data/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
- ### 3.1.0 (2017-11-42)
1
+ ### 3.2.0 (2017-11-10)
2
+
3
+ Features:
4
+
5
+ - Adds a circuit breaker to GET requests (#66)
6
+
7
+ ### 3.1.0 (2017-11-2)
2
8
 
3
9
  Features:
4
10
 
data/README.md CHANGED
@@ -329,6 +329,18 @@ response.user.show(1)
329
329
  #=> HateoasResponse
330
330
  ```
331
331
 
332
+ ### Circuit Breaker
333
+
334
+ The client ships with a circuit breaker for get requests, this can be enabled by setting `ROUTEMASTER_ENABLE_API_CLIENT_CIRCUIT`. The Circuit Breaker is powered by [Circuitbox](https://github.com/yammer/circuitbox).
335
+
336
+ The following parameters can be used:
337
+
338
+ - `ROUTEMASTER_CIRCUIT_BREAKER_SLEEP_WINDOW` - The time in seconds the circuit will remain open once it's triggered
339
+ - `ROUTEMASTER_CIRCUIT_BREAKER_TIME_WINDOW` - The time in seconds over which it calculates the error rate
340
+ - `ROUTEMASTER_CIRCUIT_BREAKER_VOLUME_THRESHOLD` - The minimum amount of requests that can trigger the circuit to open
341
+ - `ROUTEMASTER_CIRCUIT_BREAKER_ERROR_THRESHOLD` - The % of errors required for the circuit to open in the sleep window
342
+
343
+ Each of these settings can be controlled on a per domain basis. To set `ROUTEMASTER_CIRCUIT_BREAKER_SLEEP_WINDOW` for `example.com` set `example.com.ROUTEMASTER_CIRCUIT_BREAKER_SLEEP_WINDOW`
332
344
 
333
345
 
334
346
  ## Internals
@@ -8,6 +8,7 @@ require 'routemaster/middleware/response_caching'
8
8
  require 'routemaster/middleware/error_handling'
9
9
  require 'routemaster/middleware/metrics'
10
10
  require 'routemaster/responses/response_promise'
11
+ require 'routemaster/api_client_circuit'
11
12
 
12
13
  # This is not a direct dependency, we need to load it early to prevent a
13
14
  # circular dependency in hateoas_response.rb
@@ -55,13 +56,14 @@ module Routemaster
55
56
  def get(url, params: {}, headers: {}, options: {})
56
57
  enable_caching = options.fetch(:enable_caching, true)
57
58
  response_class = options[:response_class]
58
-
59
- _wrapped_response _request(
60
- :get,
61
- url: url,
62
- params: params,
63
- headers: headers.merge(response_cache_opt_headers(enable_caching))),
64
- response_class: response_class
59
+ APIClientCircuit.new(url).call do
60
+ _wrapped_response _request(
61
+ :get,
62
+ url: url,
63
+ params: params,
64
+ headers: headers.merge(response_cache_opt_headers(enable_caching))),
65
+ response_class: response_class
66
+ end
65
67
  end
66
68
 
67
69
  # Same as {{get}}, except with
@@ -0,0 +1,43 @@
1
+ require 'circuitbox'
2
+ require 'routemaster/errors'
3
+ module Routemaster
4
+ class APIClientCircuit
5
+ def initialize(url)
6
+ url = URI.parse(url) unless url.is_a? URI
7
+ @circuit_name = url.host.downcase
8
+ end
9
+
10
+ def call(&block)
11
+ if enabled?
12
+ begin
13
+ return circuit.run!(&block)
14
+ rescue Circuitbox::ServiceFailureError => e
15
+ raise e.original
16
+ end
17
+ else
18
+ return block.call
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def enabled?
25
+ ENV.fetch('ROUTEMASTER_ENABLE_API_CLIENT_CIRCUIT', 'NO') =~ /\A(YES|TRUE|ON|1)\Z/i
26
+ end
27
+
28
+ def circuit
29
+ Circuitbox.circuit(@circuit_name, {
30
+ sleep_window: configuration_setting(@circuit_name, 'ROUTEMASTER_CIRCUIT_BREAKER_SLEEP_WINDOW', 60).to_i,
31
+ time_window: configuration_setting(@circuit_name, 'ROUTEMASTER_CIRCUIT_BREAKER_TIME_WINDOW', 120).to_i,
32
+ volume_threshold: configuration_setting(@circuit_name, 'ROUTEMASTER_CIRCUIT_BREAKER_VOLUME_THRESHOLD', 50).to_i,
33
+ error_threshold: configuration_setting(@circuit_name, 'ROUTEMASTER_CIRCUIT_BREAKER_ERROR_THRESHOLD', 50).to_i,
34
+ cache: Moneta.new(:Redis, backend: Config.cache_redis),
35
+ exceptions: [Routemaster::Errors::FatalResource, Faraday::TimeoutError]
36
+ })
37
+ end
38
+
39
+ def configuration_setting(circuit_name, setting_name, default)
40
+ ENV.fetch("#{circuit_name}.#{setting_name}", ENV.fetch(setting_name, default))
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,5 @@
1
1
  module Routemaster
2
2
  module Drain
3
- VERSION = '3.1.0'.freeze
3
+ VERSION = '3.2.0'.freeze
4
4
  end
5
5
  end
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
25
25
  spec.add_runtime_dependency 'hashie'
26
26
  spec.add_runtime_dependency 'redis-namespace'
27
27
  spec.add_runtime_dependency 'concurrent-ruby'
28
+ spec.add_runtime_dependency 'circuitbox'
28
29
  end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+ require 'spec/support/uses_dotenv'
3
+ require 'spec/support/uses_redis'
4
+ require 'spec/support/uses_webmock'
5
+ require 'routemaster/api_client'
6
+ require 'routemaster/api_client_circuit'
7
+
8
+ describe Routemaster::APIClientCircuit do
9
+ uses_webmock
10
+ uses_redis
11
+
12
+ context "when enabled" do
13
+ before do
14
+ sb_req
15
+ allow_any_instance_of(described_class).to receive(:enabled?){ true }
16
+ end
17
+
18
+ let(:url){ 'http://example.com/foobar' }
19
+
20
+ let(:sb_req){
21
+ stub_request(:get, url).to_return(
22
+ status: status,
23
+ body: { id: 132, type: 'widget' }.to_json,
24
+ headers: {
25
+ 'content-type' => 'application/json;v=1'
26
+ }
27
+ )
28
+ }
29
+
30
+ def perform
31
+ Routemaster::APIClient.new.get(url)
32
+ end
33
+
34
+ context "when not erroring" do
35
+ let(:status) { 200 }
36
+
37
+ it "should pass through a response" do
38
+ expect(perform.status).to eq 200
39
+ end
40
+ end
41
+
42
+ context "when erroring" do
43
+ let(:status){ 500 }
44
+
45
+ it "should pass through a single error" do
46
+ expect{ perform }.to raise_error Routemaster::Errors::FatalResource
47
+ expect(sb_req).to have_been_requested
48
+ end
49
+
50
+ context "after lots of errors" do
51
+ before do
52
+ 60.times do
53
+ perform rescue Routemaster::Errors::FatalResource
54
+ end
55
+ end
56
+ it "should limit the amount of requests" do
57
+ expect(a_request(:get, url)).to have_been_made.at_least_times(49)
58
+ expect(a_request(:get, url)).to have_been_made.at_most_times(51)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: routemaster-drain
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien Letessier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-03 00:00:00.000000000 Z
11
+ date: 2017-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: circuitbox
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  description:
140
154
  email:
141
155
  - julien.letessier@gmail.com
@@ -167,6 +181,7 @@ files:
167
181
  - gemfiles/rails_5.gemfile
168
182
  - lib/core_ext/forwardable.rb
169
183
  - lib/routemaster/api_client.rb
184
+ - lib/routemaster/api_client_circuit.rb
170
185
  - lib/routemaster/cache.rb
171
186
  - lib/routemaster/cache_key.rb
172
187
  - lib/routemaster/config.rb
@@ -209,6 +224,7 @@ files:
209
224
  - lib/routemaster/tasks/fix_cache_ttl.rb
210
225
  - routemaster-drain.gemspec
211
226
  - spec/core_ext/forwardable_spec.rb
227
+ - spec/routemaster/api_client_circuit_spec.rb
212
228
  - spec/routemaster/api_client_spec.rb
213
229
  - spec/routemaster/cache_spec.rb
214
230
  - spec/routemaster/config_spec.rb
@@ -270,12 +286,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
270
286
  version: '0'
271
287
  requirements: []
272
288
  rubyforge_project:
273
- rubygems_version: 2.6.11
289
+ rubygems_version: 2.7.0
274
290
  signing_key:
275
291
  specification_version: 4
276
292
  summary: Event receiver for the Routemaster bus
277
293
  test_files:
278
294
  - spec/core_ext/forwardable_spec.rb
295
+ - spec/routemaster/api_client_circuit_spec.rb
279
296
  - spec/routemaster/api_client_spec.rb
280
297
  - spec/routemaster/cache_spec.rb
281
298
  - spec/routemaster/config_spec.rb