routemaster-drain 2.0.0 → 2.2.2

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
2
  SHA1:
3
- metadata.gz: ced68b1893076a1cf8df7899e43afcf468487c74
4
- data.tar.gz: 951f8e35d74922de878504e6487f64a689cea6f9
3
+ metadata.gz: 1f2f159625c82cda6149dd385d892605cbbc5381
4
+ data.tar.gz: 4355b7da645ddcaed97629f458ea056e037f1c1f
5
5
  SHA512:
6
- metadata.gz: 9d2be82bf2e1e3f4ad23687ee2a3b9d75ce1f49a9266f04e628da6608b4cdd18db86c06872f1685171d4bb61c98b465046ed75b84d6583dd53cc9edea02b1095
7
- data.tar.gz: 12281b606df8070c1d97eee13c14ba75ee33fbbe3adf9c648fec2790e6cf7a2462fee51033c75e0828a16ddf05f4df03b3210c43e490361b7c1e647ef83114a4
6
+ metadata.gz: 38adf1dbf83ed92e88ba395cb0cd1cded85c9146d9efafe112d290171663ff7f2794440eb56173e1ea20d05ff30c0353320a834a5d0d6c826294d841028fe4da
7
+ data.tar.gz: 45eac309ef2f3be2339b967b5f46075f1e314a2e07bfe32a1a6b125c4035697872071e4301c644011e4570e61b7676678187a25696e9e4bac18b2b66132ad04e
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ ### 2.2.2 (2017-01-10)
2
+
3
+ - Fix logging for error responses:
4
+ For unsuccessful responses rescue the raised error and
5
+ send increment signal to metrics backend
6
+ - Add Telemetry support for requests and responses
7
+ - Add support for PATCH requests.
8
+ Invalidate cached response (if any) on PATCH
9
+
10
+ ### 2.0.0 (2016-12-14)
11
+
12
+ Major upgrade: the gem now includes a high-level HTTP API client.
13
+
14
+ - Request materialisation, JSON-HAL parsing, and hypermedia link traversal (#6)
15
+ - Surface HTTP errors as exceptions (#8)
16
+ - Various fixes (#7, #9, #10)
17
+ - Testing against multiple versions of Rails (#11)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- routemaster-drain (2.0.0)
4
+ routemaster-drain (2.2.2)
5
5
  faraday (>= 0.9.0)
6
6
  faraday_middleware
7
7
  hashie
@@ -31,7 +31,7 @@ GEM
31
31
  diff-lcs (1.2.5)
32
32
  docile (1.1.5)
33
33
  dotenv (2.1.1)
34
- faraday (0.10.0)
34
+ faraday (0.10.1)
35
35
  multipart-post (>= 1.2, < 3)
36
36
  faraday_middleware (0.10.1)
37
37
  faraday (>= 0.7.4, < 1.0)
@@ -77,7 +77,7 @@ GEM
77
77
  pry (~> 0.10)
78
78
  psych (2.2.1)
79
79
  public_suffix (2.0.4)
80
- rack (1.4.7)
80
+ rack (1.6.5)
81
81
  rack-protection (1.5.3)
82
82
  rack
83
83
  rack-test (0.6.3)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- routemaster-drain (1.1.0)
4
+ routemaster-drain (2.2.1)
5
5
  faraday (>= 0.9.0)
6
6
  faraday_middleware
7
7
  hashie
@@ -31,6 +31,10 @@ GEM
31
31
  activesupport (= 3.2.14)
32
32
  builder (~> 3.0.0)
33
33
  activerecord (3.2.14)
34
+ activemodel (= 3.2.14)
35
+ activesupport (= 3.2.14)
36
+ arel (~> 3.0.2)
37
+ tzinfo (~> 0.3.29)
34
38
  activeresource (3.2.14)
35
39
  activemodel (= 3.2.14)
36
40
  activesupport (= 3.2.14)
@@ -43,6 +47,7 @@ GEM
43
47
  bundler
44
48
  rake
45
49
  thor (>= 0.14.0)
50
+ arel (3.0.3)
46
51
  builder (3.0.4)
47
52
  byebug (9.0.6)
48
53
  codeclimate-test-reporter (1.0.3)
@@ -108,7 +113,7 @@ GEM
108
113
  pry-byebug (3.4.2)
109
114
  byebug (~> 9.0)
110
115
  pry (~> 0.10)
111
- psych (2.2.1)
116
+ psych (2.2.2)
112
117
  public_suffix (2.0.4)
113
118
  rack (1.4.7)
114
119
  rack-cache (1.6.1)
@@ -191,6 +196,7 @@ GEM
191
196
  treetop (1.4.15)
192
197
  polyglot
193
198
  polyglot (>= 0.3.1)
199
+ tzinfo (0.3.52)
194
200
  vegas (0.1.11)
195
201
  rack (>= 1.0.0)
196
202
  webmock (2.3.1)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- routemaster-drain (1.1.0)
4
+ routemaster-drain (2.2.1)
5
5
  faraday (>= 0.9.0)
6
6
  faraday_middleware
7
7
  hashie
@@ -110,7 +110,7 @@ GEM
110
110
  pry-byebug (3.4.2)
111
111
  byebug (~> 9.0)
112
112
  pry (~> 0.10)
113
- psych (2.2.1)
113
+ psych (2.2.2)
114
114
  public_suffix (2.0.4)
115
115
  rack (1.5.5)
116
116
  rack-protection (1.5.3)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- routemaster-drain (1.1.0)
4
+ routemaster-drain (2.2.1)
5
5
  faraday (>= 0.9.0)
6
6
  faraday_middleware
7
7
  hashie
@@ -111,7 +111,7 @@ GEM
111
111
  mime-types-data (~> 3.2015)
112
112
  mime-types-data (3.2016.0521)
113
113
  mini_portile2 (2.1.0)
114
- minitest (5.9.1)
114
+ minitest (5.10.1)
115
115
  mono_logger (1.1.0)
116
116
  multi_json (1.12.1)
117
117
  multipart-post (2.0.0)
@@ -130,7 +130,7 @@ GEM
130
130
  pry-byebug (3.4.2)
131
131
  byebug (~> 9.0)
132
132
  pry (~> 0.10)
133
- psych (2.2.1)
133
+ psych (2.2.2)
134
134
  public_suffix (2.0.4)
135
135
  rack (2.0.1)
136
136
  rack-protection (1.5.3)
@@ -5,13 +5,21 @@ require 'hashie'
5
5
  require 'routemaster/config'
6
6
  require 'routemaster/middleware/response_caching'
7
7
  require 'routemaster/middleware/error_handling'
8
+ require 'routemaster/middleware/metrics'
9
+ require 'routemaster/responses/future_response'
8
10
 
9
11
  module Routemaster
10
12
  class APIClient
11
- def initialize(middlewares: [], listener: nil, response_class: nil)
13
+ def initialize(middlewares: [],
14
+ listener: nil,
15
+ response_class: nil,
16
+ metrics_client: nil,
17
+ source_peer: nil)
12
18
  @listener = listener
13
19
  @middlewares = middlewares
14
20
  @response_class = response_class
21
+ @metrics_client = metrics_client
22
+ @source_peer = source_peer
15
23
  end
16
24
 
17
25
  # Performs a GET HTTP request for the `url`, with optional
@@ -27,13 +35,38 @@ module Routemaster
27
35
  end
28
36
  end
29
37
 
38
+ def fget(url, params: {}, headers: {})
39
+ Responses::FutureResponse.new { get(url, params: {}, headers: {}) }
40
+ end
41
+
30
42
  def post(url, body: {}, headers: {})
31
43
  host = URI.parse(url).host
32
44
  response_wrapper do
33
45
  connection.post do |req|
34
46
  req.url url
35
47
  req.headers = headers.merge(auth_header(host))
36
- req.body = body.to_json
48
+ req.body = body
49
+ end
50
+ end
51
+ end
52
+
53
+ def patch(url, body: {}, headers: {})
54
+ host = URI.parse(url).host
55
+ response_wrapper do
56
+ connection.patch do |req|
57
+ req.url url
58
+ req.headers = headers.merge(auth_header(host))
59
+ req.body = body
60
+ end
61
+ end
62
+ end
63
+
64
+ def delete(url, headers: {})
65
+ host = URI.parse(url).host
66
+ response_wrapper do
67
+ connection.delete do |req|
68
+ req.url url
69
+ req.headers = headers.merge(auth_header(host))
37
70
  end
38
71
  end
39
72
  end
@@ -56,6 +89,7 @@ module Routemaster
56
89
  f.response :mashify
57
90
  f.response :json, content_type: /\bjson/
58
91
  f.use Routemaster::Middleware::ResponseCaching, listener: @listener
92
+ f.use Routemaster::Middleware::Metrics, client: @metrics_client, source_peer: @source_peer
59
93
  f.adapter :net_http_persistent
60
94
  f.use Routemaster::Middleware::ErrorHandling
61
95
 
@@ -1,9 +1,4 @@
1
1
  require 'routemaster/api_client'
2
- require 'thread/pool'
3
- require 'thread/future'
4
- require 'singleton'
5
- require 'delegate'
6
- require 'json'
7
2
  require 'wisper'
8
3
 
9
4
  module Routemaster
@@ -21,47 +16,8 @@ module Routemaster
21
16
  class Cache
22
17
  include Wisper::Publisher
23
18
 
24
- # A pool of threads, used for parallel/future request processing.
25
- class Pool < SimpleDelegator
26
- include Singleton
27
-
28
- def initialize
29
- Thread.pool(5, 20).tap do |p|
30
- # TODO: configurable pool size and trim timeout?
31
- p.auto_trim!
32
- p.idle_trim! 10 # 10 seconds
33
- super p
34
- end
35
- end
36
- end
37
-
38
- # Wraps a future response, so it quacks exactly like an ordinary response.
39
- class FutureResponse
40
- extend Forwardable
41
-
42
- # The `block` is expected to return a {Response}
43
- def initialize(&block)
44
- @future = Pool.instance.future(&block)
45
- end
46
-
47
- # @!attribute status
48
- # @return [Integer]
49
- # Delegated to the `block`'s return value.
50
-
51
- # @!attribute headers
52
- # @return [Hash]
53
- # Delegated to the `block`'s return value.
54
-
55
- # @!attribute body
56
- # @return [Hashie::Mash]
57
- # Delegated to the `block`'s return value.
58
-
59
- delegate :value => :@future
60
- delegate %i(status headers body) => :value
61
- end
62
-
63
- def initialize(redis:nil, client:nil)
64
- @redis = redis || Config.cache_redis
19
+ def initialize(redis: nil, client: nil)
20
+ @redis = redis || Config.cache_redis
65
21
  @client = client || APIClient.new(listener: self)
66
22
  end
67
23
 
@@ -87,13 +43,7 @@ module Routemaster
87
43
  #
88
44
  # @return [Response], which responds to `status`, `headers`, and `body`.
89
45
  def get(url, version: nil, locale: nil)
90
- headers = {
91
- 'Accept' => version ?
92
- "application/json;v=#{version}" :
93
- "application/json"
94
- }
95
- headers['Accept-Language'] = locale if locale
96
- @client.get(url, headers: headers)
46
+ @client.get(url, headers: headers(version: version, locale: locale))
97
47
  end
98
48
 
99
49
  # Like {#get}, but schedules any request in the background using a thread
@@ -102,7 +52,16 @@ module Routemaster
102
52
  # @return [FutureResponse], which responds to `status`, `headers`, and `body`
103
53
  # like [Response].
104
54
  def fget(url, version: nil, locale: nil)
105
- FutureResponse.new { get(url, version: version, locale: locale) }
55
+ @client.fget(url, headers: headers(version: version, locale: locale))
56
+ end
57
+
58
+ private
59
+
60
+ def headers(version: nil, locale: nil)
61
+ @headers ||= {}.tap do |hash|
62
+ hash['Accept'] = version ? "application/json;v=#{version}" : "application/json"
63
+ hash['Accept-Language'] = locale if locale
64
+ end
106
65
  end
107
66
  end
108
67
  end
@@ -1,5 +1,5 @@
1
1
  module Routemaster
2
2
  module Drain
3
- VERSION = '2.0.0'
3
+ VERSION = '2.2.2'
4
4
  end
5
5
  end
@@ -9,7 +9,7 @@ module Routemaster
9
9
  end
10
10
 
11
11
  def errors
12
- body['errors']
12
+ body.fetch('errors', {})
13
13
  end
14
14
 
15
15
  def message
@@ -27,44 +27,44 @@ module Routemaster
27
27
  end
28
28
  end
29
29
 
30
- class UnauthorizedResourceAccessError < BaseError
30
+ class UnauthorizedResourceAccess < BaseError
31
31
  def message
32
32
  "Unauthorized Resource Access Error"
33
33
  end
34
34
  end
35
35
 
36
- class InvalidResourceError < BaseError
36
+ class InvalidResource < BaseError
37
37
  def message
38
38
  "Invalid Resource Error"
39
39
  end
40
40
  end
41
41
 
42
- class ResourceNotFoundError < BaseError
42
+ class ResourceNotFound < BaseError
43
43
  def message
44
44
  "Resource Not Found Error"
45
45
  end
46
46
  end
47
47
 
48
- class FatalResourceError < BaseError
48
+ class FatalResource < BaseError
49
49
  def message
50
50
  "Fatal Resource Error. body: #{body}, url: #{env.url}, method: #{env.method}"
51
51
  end
52
52
  end
53
53
 
54
- class ConflictResourceError < BaseError
54
+ class ConflictResource < BaseError
55
55
  def message
56
56
  "ConflictResourceError Resource Error"
57
57
  end
58
58
  end
59
59
 
60
- class IncompatibleVersionError < BaseError
60
+ class IncompatibleVersion < BaseError
61
61
  def message
62
62
  headers = env.request_headers.select { |k, _| k != 'Authorization' }
63
63
  "Incompatible Version Error. headers: #{headers}, url: #{env.url}, method: #{env.method}"
64
64
  end
65
65
  end
66
66
 
67
- class ResourceThrottlingError < BaseError
67
+ class ResourceThrottling < BaseError
68
68
  def message
69
69
  "Resource Throttling Error"
70
70
  end
@@ -5,15 +5,15 @@ module Routemaster
5
5
  module Middleware
6
6
  class ErrorHandling < Faraday::Response::Middleware
7
7
  ERRORS_MAPPING = {
8
- (400..400) => Errors::InvalidResourceError,
9
- (401..401) => Errors::UnauthorizedResourceAccessError,
10
- (403..403) => Errors::UnauthorizedResourceAccessError,
11
- (404..404) => Errors::ResourceNotFoundError,
12
- (409..409) => Errors::ConflictResourceError,
13
- (412..412) => Errors::IncompatibleVersionError,
14
- (413..413) => Errors::InvalidResourceError,
15
- (429..429) => Errors::ResourceThrottlingError,
16
- (407..500) => Errors::FatalResourceError
8
+ (400..400) => Errors::InvalidResource,
9
+ (401..401) => Errors::UnauthorizedResourceAccess,
10
+ (403..403) => Errors::UnauthorizedResourceAccess,
11
+ (404..404) => Errors::ResourceNotFound,
12
+ (409..409) => Errors::ConflictResource,
13
+ (412..412) => Errors::IncompatibleVersion,
14
+ (413..413) => Errors::InvalidResource,
15
+ (429..429) => Errors::ResourceThrottling,
16
+ (407..500) => Errors::FatalResource
17
17
  }.freeze
18
18
 
19
19
  def on_complete(env)
@@ -0,0 +1,71 @@
1
+ module Routemaster
2
+ module Middleware
3
+ class Metrics
4
+ INTERACTION_KEY = 'api_client'.freeze
5
+
6
+ def initialize(app, client: nil, source_peer: nil)
7
+ @app = app
8
+ @client = client
9
+ @source_peer = source_peer
10
+ end
11
+
12
+ def call(request_env)
13
+ return @app.call(request_env) unless can_log?
14
+
15
+ increment_req_count(request_tags(request_env))
16
+
17
+ record_latency(request_tags(request_env)) do
18
+ begin
19
+ @app.call(request_env).on_complete do |response_env|
20
+ increment_response_count(response_tags(response_env))
21
+ end
22
+ rescue Routemaster::Errors::BaseError => e
23
+ increment_response_count(response_tags(e.env))
24
+ raise e
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :client, :source_peer
32
+
33
+ def increment_req_count(tags)
34
+ client.increment("#{INTERACTION_KEY}.request.count", tags: tags)
35
+ end
36
+
37
+ def increment_response_count(tags)
38
+ client.increment("#{INTERACTION_KEY}.response.count", tags: tags)
39
+ end
40
+
41
+ def record_latency(tags, &block)
42
+ client.time("#{INTERACTION_KEY}.latency", tags: tags) do
43
+ block.call
44
+ end
45
+ end
46
+
47
+ def can_log?
48
+ client && source_peer
49
+ end
50
+
51
+ def destination_peer(env)
52
+ env.url.host
53
+ end
54
+
55
+ def peers_tags(env)
56
+ [
57
+ "source:#{source_peer}",
58
+ "destination:#{destination_peer(env)}"
59
+ ]
60
+ end
61
+
62
+ def request_tags(env)
63
+ peers_tags(env).concat(["verb:#{env.method}"])
64
+ end
65
+
66
+ def response_tags(env)
67
+ peers_tags(env).concat(["status:#{env.status}"])
68
+ end
69
+ end
70
+ end
71
+ end
@@ -16,7 +16,8 @@ module Routemaster
16
16
  end
17
17
 
18
18
  def call(env)
19
- return @app.call(env) unless env.method == :get
19
+ @cache.del(cache_key(env)) if %i(patch delete).include?(env.method)
20
+ return @app.call(env) if env.method != :get
20
21
 
21
22
  fetch_from_cache(env) || fetch_from_service(env)
22
23
  end
@@ -21,6 +21,14 @@ module Routemaster
21
21
  def index
22
22
  @client.get(@url)
23
23
  end
24
+
25
+ def update(id=nil, params)
26
+ @client.patch(@url.gsub('{id}', id.to_s), body: params)
27
+ end
28
+
29
+ def destroy(id=nil)
30
+ @client.delete(@url.gsub('{id}', id.to_s))
31
+ end
24
32
  end
25
33
  end
26
34
  end
@@ -0,0 +1,46 @@
1
+ require 'thread/pool'
2
+ require 'thread/future'
3
+ require 'singleton'
4
+ require 'delegate'
5
+
6
+ module Routemaster
7
+ module Responses
8
+ # A pool of threads, used for parallel/future request processing.
9
+ class Pool < SimpleDelegator
10
+ include Singleton
11
+
12
+ def initialize
13
+ Thread.pool(5, 20).tap do |p|
14
+ # TODO: configurable pool size and trim timeout?
15
+ p.auto_trim!
16
+ p.idle_trim! 10 # 10 seconds
17
+ super p
18
+ end
19
+ end
20
+ end
21
+
22
+ class FutureResponse
23
+ extend Forwardable
24
+
25
+ # The `block` is expected to return a {Response}
26
+ def initialize(&block)
27
+ @future = Pool.instance.future(&block)
28
+ end
29
+
30
+ # @!attribute status
31
+ # @return [Integer]
32
+ # Delegated to the `block`'s return value.
33
+
34
+ # @!attribute headers
35
+ # @return [Hash]
36
+ # Delegated to the `block`'s return value.
37
+
38
+ # @!attribute body
39
+ # @return [Hashie::Mash]
40
+ # Delegated to the `block`'s return value.
41
+
42
+ delegate :value => :@future
43
+ delegate %i(status headers body) => :value
44
+ end
45
+ end
46
+ end
@@ -26,7 +26,7 @@ module Routemaster
26
26
  unless respond_to?(method_name)
27
27
  resource = Resources::RestResource.new(_links[normalized_method_name]['href'], client: @client)
28
28
 
29
- self.class.send(:define_method, method_name) do |*m_args|
29
+ define_singleton_method(method_name) do |*m_args|
30
30
  resource
31
31
  end
32
32
 
@@ -32,6 +32,14 @@ describe Routemaster::APIClient do
32
32
  'content-type' => 'application/json;v=1'
33
33
  }
34
34
  )
35
+
36
+ @patch_req = stub_request(:patch, /example\.com/).to_return(
37
+ status: 200,
38
+ body: { id: 132, type: 'widget' }.to_json,
39
+ headers: {
40
+ 'content-type' => 'application/json;v=1'
41
+ }
42
+ )
35
43
  end
36
44
 
37
45
  it 'GETs from the URL' do
@@ -48,6 +56,15 @@ describe Routemaster::APIClient do
48
56
  end
49
57
  end
50
58
 
59
+ context 'PATCH request' do
60
+ subject { fetcher.patch(url, body: {}, headers: headers) }
61
+
62
+ it 'PATCH from the URL' do
63
+ subject
64
+ expect(@patch_req).to have_been_requested
65
+ end
66
+ end
67
+
51
68
  it 'has :status, :headers, :body' do
52
69
  expect(subject.status).to eq(200)
53
70
  expect(subject.headers).to have_key('content-type')
@@ -70,12 +70,6 @@ module Routemaster
70
70
  it_behaves_like 'a GET request'
71
71
  end
72
72
 
73
- describe '#fget' do
74
- let(:perform) { subject.fget(url, **options) }
75
-
76
- it_behaves_like 'a GET request'
77
- end
78
-
79
73
  describe '#bust' do
80
74
  let(:cache) { Config.cache_redis }
81
75
  let(:perform) { subject.bust(url) }
@@ -5,6 +5,14 @@ require 'routemaster/api_client'
5
5
  require 'webrick'
6
6
 
7
7
  RSpec.describe 'Api client integration specs' do
8
+ module WEBrick
9
+ module HTTPServlet
10
+ class ProcHandler
11
+ alias do_PATCH do_GET
12
+ end
13
+ end
14
+ end
15
+
8
16
  uses_dotenv
9
17
  uses_redis
10
18
 
@@ -17,6 +25,11 @@ RSpec.describe 'Api client integration specs' do
17
25
  res.body = { field: 'test' }.to_json
18
26
  end
19
27
  end
28
+
29
+ server.mount_proc "/success" do |req, res|
30
+ res.status = 200
31
+ res.body = { field: 'test' }.to_json
32
+ end
20
33
  end
21
34
  end
22
35
 
@@ -37,40 +50,143 @@ RSpec.describe 'Api client integration specs' do
37
50
  let(:host) { 'http://localhost:8000' }
38
51
 
39
52
  describe 'error handling' do
40
- it 'raises an ResourceNotFoundError on 404' do
41
- expect { subject.get(host + '/404') }.to raise_error(Routemaster::Errors::ResourceNotFoundError)
53
+ it 'raises an ResourceNotFound on 404' do
54
+ expect { subject.get(host + '/404') }.to raise_error(Routemaster::Errors::ResourceNotFound)
55
+ end
56
+
57
+ it 'raises an InvalidResource on 400' do
58
+ expect { subject.get(host + '/400') }.to raise_error(Routemaster::Errors::InvalidResource)
59
+ end
60
+
61
+ it 'raises an UnauthorizedResourceAccess on 401' do
62
+ expect { subject.get(host + '/401') }.to raise_error(Routemaster::Errors::UnauthorizedResourceAccess)
63
+ end
64
+
65
+ it 'raises an UnauthorizedResourceAccess on 403' do
66
+ expect { subject.get(host + '/403') }.to raise_error(Routemaster::Errors::UnauthorizedResourceAccess)
42
67
  end
43
68
 
44
- it 'raises an InvalidResourceError on 400' do
45
- expect { subject.get(host + '/400') }.to raise_error(Routemaster::Errors::InvalidResourceError)
69
+ it 'raises an ConflictResource on 409' do
70
+ expect { subject.get(host + '/409') }.to raise_error(Routemaster::Errors::ConflictResource)
46
71
  end
47
72
 
48
- it 'raises an UnauthorizedResourceAccessError on 401' do
49
- expect { subject.get(host + '/401') }.to raise_error(Routemaster::Errors::UnauthorizedResourceAccessError)
73
+ it 'raises an IncompatibleVersion on 412' do
74
+ expect { subject.get(host + '/412') }.to raise_error(Routemaster::Errors::IncompatibleVersion)
75
+ end
76
+
77
+ it 'raises an InvalidResource on 413' do
78
+ expect { subject.get(host + '/413') }.to raise_error(Routemaster::Errors::InvalidResource)
79
+ end
80
+
81
+ it 'raises an ResourceThrottling on 429' do
82
+ expect { subject.get(host + '/429') }.to raise_error(Routemaster::Errors::ResourceThrottling)
83
+ end
84
+
85
+ it 'raises an FatalResource on 500' do
86
+ expect { subject.get(host + '/500') }.to raise_error(Routemaster::Errors::FatalResource)
87
+ end
88
+ end
89
+
90
+ describe 'Future GET request' do
91
+ let(:body_cache_keys) { ["cache:#{url}", "v:,l:,body"] }
92
+ let(:headers_cache_keys) { ["cache:#{url}", "v:,l:,headers"] }
93
+ let(:url) { "#{host}/success" }
94
+
95
+ context 'when there is a previous cached resource' do
96
+ let(:cache) { Routemaster::Config.cache_redis }
97
+
98
+ it 'returns the response from within the future' do
99
+ expect(cache.hget("cache:#{url}", "v:,l:,body")).to be_nil
100
+
101
+ future = subject.fget(url)
102
+ expect(future).to be_an_instance_of(Routemaster::Responses::FutureResponse)
103
+
104
+ future.value
105
+ expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
106
+ end
50
107
  end
108
+ end
109
+
110
+ describe 'PATCH request' do
111
+ let(:body_cache_keys) { ["cache:#{url}", "v:,l:,body"] }
112
+ let(:headers_cache_keys) { ["cache:#{url}", "v:,l:,headers"] }
113
+ let(:url) { "#{host}/success" }
114
+
115
+ context 'when there is a previous cached resource' do
116
+ before { subject.get(url) }
117
+ let(:cache) { Routemaster::Config.cache_redis }
51
118
 
52
- it 'raises an UnauthorizedResourceAccessError on 403' do
53
- expect { subject.get(host + '/403') }.to raise_error(Routemaster::Errors::UnauthorizedResourceAccessError)
119
+ it 'invalidates the cache on update' do
120
+ expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
121
+ subject.patch(url, body: {})
122
+
123
+ expect(cache.hget("cache:#{url}", "v:,l:,body")).to be_nil
124
+
125
+ subject.get(url)
126
+ expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
127
+ end
54
128
  end
129
+ end
130
+
131
+ describe 'DELETE request' do
132
+ let(:body_cache_keys) { ["cache:#{url}", "v:,l:,body"] }
133
+ let(:headers_cache_keys) { ["cache:#{url}", "v:,l:,headers"] }
134
+ let(:url) { "#{host}/success" }
135
+
136
+ context 'when there is a previous cached resource' do
137
+ before { subject.get(url) }
138
+ let(:cache) { Routemaster::Config.cache_redis }
55
139
 
56
- it 'raises an ConflictResourceError on 409' do
57
- expect { subject.get(host + '/409') }.to raise_error(Routemaster::Errors::ConflictResourceError)
140
+ it 'invalidates the cache on destroy' do
141
+ expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
142
+ subject.delete(url)
143
+
144
+ expect(cache.hget("cache:#{url}", "v:,l:,body")).to be_nil
145
+
146
+ subject.get(url)
147
+ expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
148
+ end
58
149
  end
150
+ end
151
+
152
+ describe 'Telemetry' do
153
+ let(:metrics_client) { double('MetricsClient') }
154
+ let(:source_peer) { 'test_service' }
155
+ let(:url) { "#{host}/success" }
59
156
 
60
- it 'raises an IncompatibleVersionError on 412' do
61
- expect { subject.get(host + '/412') }.to raise_error(Routemaster::Errors::IncompatibleVersionError)
157
+ subject do
158
+ Routemaster::APIClient.new(metrics_client: metrics_client,
159
+ source_peer: source_peer)
62
160
  end
63
161
 
64
- it 'raises an InvalidResourceError on 413' do
65
- expect { subject.get(host + '/413') }.to raise_error(Routemaster::Errors::InvalidResourceError)
162
+ context 'when metrics source peer is absent' do
163
+ subject { Routemaster::APIClient.new(metrics_client: metrics_client) }
164
+
165
+ it 'does not send metrics' do
166
+ expect(metrics_client).to receive(:increment).never
167
+ subject.get(url)
168
+ end
66
169
  end
67
170
 
68
- it 'raises an ResourceThrottlingError on 429' do
69
- expect { subject.get(host + '/429') }.to raise_error(Routemaster::Errors::ResourceThrottlingError)
171
+ it 'does send request metrics' do
172
+ allow(metrics_client).to receive(:time).and_yield
173
+ allow(metrics_client).to receive(:increment)
174
+ expected_req_count_tags = ["source:test_service", "destination:localhost", "verb:get"]
175
+
176
+ expect(metrics_client).to receive(:increment).with('api_client.request.count', tags: expected_req_count_tags)
177
+
178
+ subject.get(url)
70
179
  end
71
180
 
72
- it 'raises an FatalResourceError on 500' do
73
- expect { subject.get(host + '/500') }.to raise_error(Routemaster::Errors::FatalResourceError)
181
+ it 'does send response metrics' do
182
+ allow(metrics_client).to receive(:increment)
183
+ expected_res_count_tags = ["source:test_service", "destination:localhost", "status:200"]
184
+ expected_latency_tags = ["source:test_service", "destination:localhost", "verb:get"]
185
+
186
+ expect(metrics_client).to receive(:increment).with('api_client.response.count', tags: expected_res_count_tags)
187
+ expect(metrics_client).to receive(:time).with('api_client.latency', tags: expected_latency_tags).and_yield
188
+
189
+ subject.get(url)
74
190
  end
75
191
  end
76
192
  end
@@ -58,9 +58,7 @@ RSpec.describe 'Requests with caching' do
58
58
  end
59
59
 
60
60
  context 'when there is a previous cached response' do
61
- before do
62
- subject.get(url)
63
- end
61
+ before { subject.get(url) }
64
62
 
65
63
  it 'fetches the cached response' do
66
64
  expect(subject.get(url).body).to eq({ field: 'test' }.to_json)
@@ -30,6 +30,20 @@ module Routemaster
30
30
  subject.index
31
31
  end
32
32
  end
33
+
34
+ describe '#update' do
35
+ it 'updates the given resource' do
36
+ expect(client).to receive(:patch).with(url, body: params)
37
+ subject.update(1, params)
38
+ end
39
+ end
40
+
41
+ describe '#destroy' do
42
+ it 'destroys the given resource' do
43
+ expect(client).to receive(:delete).with(url)
44
+ subject.destroy(1)
45
+ end
46
+ end
33
47
  end
34
48
  end
35
49
  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: 2.0.0
4
+ version: 2.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien Letessier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-14 00:00:00.000000000 Z
11
+ date: 2017-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -137,6 +137,7 @@ files:
137
137
  - ".travis.yml"
138
138
  - ".yardopts"
139
139
  - Appraisals
140
+ - CHANGELOG.md
140
141
  - Gemfile
141
142
  - Gemfile.lock
142
143
  - Guardfile
@@ -172,11 +173,13 @@ files:
172
173
  - lib/routemaster/middleware/dirty.rb
173
174
  - lib/routemaster/middleware/error_handling.rb
174
175
  - lib/routemaster/middleware/filter.rb
176
+ - lib/routemaster/middleware/metrics.rb
175
177
  - lib/routemaster/middleware/parse.rb
176
178
  - lib/routemaster/middleware/response_caching.rb
177
179
  - lib/routemaster/middleware/root_post_only.rb
178
180
  - lib/routemaster/redis_broker.rb
179
181
  - lib/routemaster/resources/rest_resource.rb
182
+ - lib/routemaster/responses/future_response.rb
180
183
  - lib/routemaster/responses/hateoas_response.rb
181
184
  - routemaster-drain.gemspec
182
185
  - spec/routemaster/api_client_spec.rb