routemaster-drain 3.1.0 → 3.2.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 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