routemaster-drain 2.0.0 → 2.2.2

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: 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