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 +5 -5
- data/CHANGELOG.md +7 -1
- data/README.md +12 -0
- data/lib/routemaster/api_client.rb +9 -7
- data/lib/routemaster/api_client_circuit.rb +43 -0
- data/lib/routemaster/drain.rb +1 -1
- data/routemaster-drain.gemspec +1 -0
- data/spec/routemaster/api_client_circuit_spec.rb +63 -0
- metadata +20 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1ddda71d2b877bc368adcfb3d6f97d6c79916b8b2d72db9036d4bdf34ae7f5d8
|
|
4
|
+
data.tar.gz: 2c3371a3ed8b1df43f743bbc3d9e715fc8ea63ea796042b922f564c13da45467
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4034317d864f77c4b111f0837673fc3bc9b7f1cb3f93fa79aeff4170b2d80dcf9f6a3cc58072a5a6819791013612a04c59a34baee67b2380ac0a36dd66a8c52
|
|
7
|
+
data.tar.gz: 51b7c394000cbe0efe657f53ea00b7d4d99063675796a78e8ee3f72e8708e18256311c93792bcd98875a022ac80bc29c20f6eaa034ae178ad6f6ef3196093a38
|
data/CHANGELOG.md
CHANGED
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
data/lib/routemaster/drain.rb
CHANGED
data/routemaster-drain.gemspec
CHANGED
|
@@ -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.
|
|
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-
|
|
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.
|
|
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
|