cottus 0.2.0 → 0.3.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 +4 -4
- data/README.md +11 -11
- data/lib/cottus.rb +1 -1
- data/lib/cottus/client.rb +1 -5
- data/lib/cottus/connection.rb +8 -4
- data/lib/cottus/forward.rb +3 -3
- data/lib/cottus/strategies.rb +6 -5
- data/lib/cottus/version.rb +1 -1
- data/spec/acceptance/cottus_acceptance_spec.rb +2 -24
- data/spec/cottus/client_spec.rb +4 -4
- data/spec/cottus/strategies_spec.rb +53 -41
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b664ce094bd30b48d6e6a4f0bc136d9b78a27e0e
|
4
|
+
data.tar.gz: 6cedfbfa25af3845515d43ba47305ea0402d3c8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc7a3b6eccaab7606cc74397f3c5b4a1d7c8b545938aebbec7ec0642512282b5d60c447316239668cae5e8326e40bf1e5583c36f8533d84c87186a35dbe80235
|
7
|
+
data.tar.gz: f80fc8cc0939e4980d43e9315e2290a3c19200abb63cbdcc9c056c26e0d0be7e4cf204cb0918dee4c8fc6a36c64d968080b4a3849528fe9d588107314cea3342
|
data/README.md
CHANGED
@@ -36,17 +36,17 @@ response = client.post('/any/path', :query => {:id => 1337}, :body => { :attribu
|
|
36
36
|
puts response.body, response.code, response.message, response.headers.inspect
|
37
37
|
```
|
38
38
|
|
39
|
-
That's about it! Cottus exposes almost all of the same methods with
|
40
|
-
|
39
|
+
That's about it! Cottus exposes almost all of the same methods with similar semantics as
|
40
|
+
Excon does.
|
41
41
|
|
42
42
|
## Strategy
|
43
43
|
|
44
|
-
A "Strategy" is merely a class implementing an
|
45
|
-
responsible for carrying out the action specified by the passed
|
44
|
+
A "Strategy" is merely a class implementing an `execute` method that is
|
45
|
+
responsible for carrying out the action specified by the passed `meth`
|
46
46
|
argument.
|
47
47
|
|
48
|
-
The Strategy class must however also implement an
|
49
|
-
takes two parameters:
|
48
|
+
The Strategy class must however also implement an `#initialize` method which
|
49
|
+
takes two parameters: `connections` and an `options` hash:
|
50
50
|
|
51
51
|
```ruby
|
52
52
|
class SomeStrategy
|
@@ -59,7 +59,7 @@ class SomeStrategy
|
|
59
59
|
end
|
60
60
|
```
|
61
61
|
|
62
|
-
If you don't mind inheritance there's a base class (
|
62
|
+
If you don't mind inheritance there's a base class (`Cottus::Strategy`) that
|
63
63
|
you can inherit from and the above class would instead become:
|
64
64
|
|
65
65
|
```ruby
|
@@ -71,13 +71,13 @@ end
|
|
71
71
|
```
|
72
72
|
|
73
73
|
If you'd like to do some initialization on your own and override
|
74
|
-
|
75
|
-
variable (
|
74
|
+
`#initialize` make sure to call `#super` or set the required instance
|
75
|
+
variable (`@connections`) on your own.
|
76
76
|
|
77
77
|
It should be noted that I haven't decided on how strategies should be working to
|
78
78
|
a 100% yet, so this might change in future releases.
|
79
79
|
|
80
|
-
See
|
80
|
+
See `lib/cottus/strategies.rb` for further examples.
|
81
81
|
|
82
82
|
### Using your own strategy
|
83
83
|
|
@@ -92,7 +92,7 @@ client = Cottus::Client.new(['http://n1.com', 'http://n2.com'], :strategy => MyS
|
|
92
92
|
|
93
93
|
Want some additional options passed when your strategy is initialized?
|
94
94
|
|
95
|
-
No problem! Pass them into the
|
95
|
+
No problem! Pass them into the `strategy_options` sub-hash of the options
|
96
96
|
hash to the client.
|
97
97
|
|
98
98
|
```ruby
|
data/lib/cottus.rb
CHANGED
data/lib/cottus/client.rb
CHANGED
@@ -21,7 +21,7 @@ module Cottus
|
|
21
21
|
|
22
22
|
def create_connections(hosts)
|
23
23
|
hosts = hosts.is_a?(String) ? hosts.split(',') : hosts
|
24
|
-
hosts.map { |host| Connection.new(
|
24
|
+
hosts.map { |host| Connection.new(Excon.new(host)) }
|
25
25
|
end
|
26
26
|
|
27
27
|
def create_strategy(options)
|
@@ -29,9 +29,5 @@ module Cottus
|
|
29
29
|
strategy_impl = options[:strategy] || RoundRobinStrategy
|
30
30
|
strategy_impl.new(connections, strategy_options)
|
31
31
|
end
|
32
|
-
|
33
|
-
def http
|
34
|
-
HTTParty
|
35
|
-
end
|
36
32
|
end
|
37
33
|
end
|
data/lib/cottus/connection.rb
CHANGED
@@ -8,14 +8,18 @@ module Cottus
|
|
8
8
|
|
9
9
|
attr_reader :host
|
10
10
|
|
11
|
-
def initialize(
|
12
|
-
@
|
11
|
+
def initialize(connection)
|
12
|
+
@connection = connection
|
13
|
+
end
|
14
|
+
|
15
|
+
def host
|
16
|
+
@connection.data[:host]
|
13
17
|
end
|
14
18
|
|
15
19
|
private
|
16
20
|
|
17
|
-
def wrapper(verb, path, options={}
|
18
|
-
@
|
21
|
+
def wrapper(verb, path, options={})
|
22
|
+
@connection.send(verb, options.merge({path: path}))
|
19
23
|
end
|
20
24
|
end
|
21
25
|
end
|
data/lib/cottus/forward.rb
CHANGED
@@ -7,11 +7,11 @@ module Cottus
|
|
7
7
|
to, through = options.values_at(:to, :through)
|
8
8
|
|
9
9
|
args.each do |verb|
|
10
|
-
define_method(verb) do |path, opts={}
|
10
|
+
define_method(verb) do |path, opts={}|
|
11
11
|
if to && through
|
12
|
-
instance_variable_get(to).send(through, verb, path, opts
|
12
|
+
instance_variable_get(to).send(through, verb, path, opts)
|
13
13
|
else
|
14
|
-
self.send(to, verb, path, opts
|
14
|
+
self.send(to, verb, path, opts)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
data/lib/cottus/strategies.rb
CHANGED
@@ -4,6 +4,7 @@ module Cottus
|
|
4
4
|
|
5
5
|
VALID_EXCEPTIONS = [
|
6
6
|
Timeout::Error,
|
7
|
+
Excon::Errors::Timeout,
|
7
8
|
Errno::ECONNREFUSED,
|
8
9
|
Errno::ETIMEDOUT,
|
9
10
|
Errno::ECONNRESET
|
@@ -14,7 +15,7 @@ module Cottus
|
|
14
15
|
@connections = connections
|
15
16
|
end
|
16
17
|
|
17
|
-
def execute(meth, path, options={}
|
18
|
+
def execute(meth, path, options={})
|
18
19
|
raise NotImplementedError, 'implement me in subclass'
|
19
20
|
end
|
20
21
|
end
|
@@ -27,11 +28,11 @@ module Cottus
|
|
27
28
|
@mutex = Mutex.new
|
28
29
|
end
|
29
30
|
|
30
|
-
def execute(meth, path, options={}
|
31
|
+
def execute(meth, path, options={})
|
31
32
|
tries = 0
|
32
33
|
|
33
34
|
begin
|
34
|
-
next_connection.send(meth, path, options
|
35
|
+
next_connection.send(meth, path, options)
|
35
36
|
rescue *VALID_EXCEPTIONS => e
|
36
37
|
if tries >= @connections.count
|
37
38
|
raise e
|
@@ -60,12 +61,12 @@ module Cottus
|
|
60
61
|
@timeouts = options[:timeouts] || [1, 3, 5]
|
61
62
|
end
|
62
63
|
|
63
|
-
def execute(meth, path, options={}
|
64
|
+
def execute(meth, path, options={})
|
64
65
|
tries = 0
|
65
66
|
starting_connection = connection = next_connection
|
66
67
|
|
67
68
|
begin
|
68
|
-
connection.send(meth, path, options
|
69
|
+
connection.send(meth, path, options)
|
69
70
|
rescue *VALID_EXCEPTIONS => e
|
70
71
|
if tries < @timeouts.size
|
71
72
|
sleep @timeouts[tries]
|
data/lib/cottus/version.rb
CHANGED
@@ -6,7 +6,7 @@ module Cottus
|
|
6
6
|
describe 'Client acceptance spec' do
|
7
7
|
shared_examples 'exception handling' do
|
8
8
|
context 'exceptions' do
|
9
|
-
context 'Timeout
|
9
|
+
context 'Excon::Errors::Timeout' do
|
10
10
|
it 'attempts to use each host until one succeeds' do
|
11
11
|
stub_request(verb, 'http://localhost:1234/some/path').to_timeout
|
12
12
|
stub_request(verb, 'http://localhost:12345/some/path').to_timeout
|
@@ -21,7 +21,7 @@ module Cottus
|
|
21
21
|
stub_request(verb, 'http://localhost:12345/some/path').to_timeout
|
22
22
|
stub_request(verb, 'http://localhost:12343/some/path').to_timeout
|
23
23
|
|
24
|
-
expect { client.send(verb, '/some/path') }.to raise_error(Timeout
|
24
|
+
expect { client.send(verb, '/some/path') }.to raise_error(Excon::Errors::Timeout)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -147,16 +147,6 @@ module Cottus
|
|
147
147
|
end
|
148
148
|
end
|
149
149
|
|
150
|
-
describe '#move' do
|
151
|
-
include_examples 'load balancing' do
|
152
|
-
let(:verb) { :move }
|
153
|
-
end
|
154
|
-
|
155
|
-
include_examples 'exception handling' do
|
156
|
-
let(:verb) { :move }
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
150
|
describe '#options' do
|
161
151
|
include_examples 'load balancing' do
|
162
152
|
let(:verb) { :options }
|
@@ -166,17 +156,5 @@ module Cottus
|
|
166
156
|
let(:verb) { :options }
|
167
157
|
end
|
168
158
|
end
|
169
|
-
|
170
|
-
describe '#copy' do
|
171
|
-
pending do
|
172
|
-
include_examples 'load balancing' do
|
173
|
-
let(:verb) { :options }
|
174
|
-
end
|
175
|
-
|
176
|
-
include_examples 'exception handling' do
|
177
|
-
let(:verb) { :options }
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
159
|
end
|
182
160
|
end
|
data/spec/cottus/client_spec.rb
CHANGED
@@ -6,13 +6,13 @@ module Cottus
|
|
6
6
|
describe Client do
|
7
7
|
describe '#initialize' do
|
8
8
|
it 'accepts an array of hosts w/ ports' do
|
9
|
-
client = described_class.new(['host1:123', 'host2:125'])
|
10
|
-
expect(client.hosts).to eq ['host1
|
9
|
+
client = described_class.new(['http://host1:123', 'http://host2:125'])
|
10
|
+
expect(client.hosts).to eq ['host1', 'host2']
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'accepts a connection string w/ ports' do
|
14
|
-
client = described_class.new('host1:1255,host2:1255,host3:1255')
|
15
|
-
expect(client.hosts).to eq ['host1
|
14
|
+
client = described_class.new('http://host1:1255,http://host2:1255,http://host3:1255')
|
15
|
+
expect(client.hosts).to eq ['host1', 'host2', 'host3']
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -8,16 +8,20 @@ module Cottus
|
|
8
8
|
described_class.new(connections)
|
9
9
|
end
|
10
10
|
|
11
|
-
let :
|
11
|
+
let :first do
|
12
12
|
double(:http, get: nil)
|
13
13
|
end
|
14
14
|
|
15
|
-
let :
|
16
|
-
|
15
|
+
let :second do
|
16
|
+
double(:http, get: nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
let :third do
|
20
|
+
double(:http, get: nil)
|
17
21
|
end
|
18
22
|
|
19
23
|
let :connections do
|
20
|
-
|
24
|
+
[first, second, third]
|
21
25
|
end
|
22
26
|
|
23
27
|
describe '#execute' do
|
@@ -29,47 +33,47 @@ module Cottus
|
|
29
33
|
|
30
34
|
shared_examples 'a round-robin strategy' do
|
31
35
|
context 'with a single host' do
|
32
|
-
let :
|
33
|
-
[
|
36
|
+
let :connections do
|
37
|
+
[first]
|
34
38
|
end
|
35
39
|
|
36
40
|
it 'uses the single host for the first request' do
|
37
41
|
strategy.execute(:get, '/some/path', query: { query: 1 })
|
38
42
|
|
39
|
-
expect(
|
43
|
+
expect(first).to have_received(:get).with('/some/path', query: { query: 1 }).once
|
40
44
|
end
|
41
45
|
|
42
46
|
it 'uses the single host for the second request' do
|
43
47
|
2.times { strategy.execute(:get, '/some/path', query: { query: 1 }) }
|
44
48
|
|
45
|
-
expect(
|
49
|
+
expect(first).to have_received(:get).with('/some/path', query: { query: 1 }).twice
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
49
53
|
context 'with several hosts' do
|
50
|
-
let :
|
51
|
-
[
|
54
|
+
let :connections do
|
55
|
+
[first, second, third]
|
52
56
|
end
|
53
57
|
|
54
58
|
it 'uses the first host for the first request' do
|
55
59
|
strategy.execute(:get, '/some/path', query: { query: 1 })
|
56
60
|
|
57
|
-
expect(
|
61
|
+
expect(first).to have_received(:get).with('/some/path', query: { query: 1 }).once
|
58
62
|
end
|
59
63
|
|
60
64
|
it 'uses the second host for the second request' do
|
61
65
|
2.times { strategy.execute(:get, '/some/path', query: { query: 1 }) }
|
62
66
|
|
63
|
-
expect(
|
64
|
-
expect(
|
67
|
+
expect(first).to have_received(:get).with('/some/path', query: { query: 1 }).once
|
68
|
+
expect(second).to have_received(:get).with('/some/path', query: { query: 1 }).once
|
65
69
|
end
|
66
70
|
|
67
71
|
it 'uses each host in turn' do
|
68
72
|
3.times { strategy.execute(:get, '/some/path', query: { query: 1 }) }
|
69
73
|
|
70
|
-
expect(
|
71
|
-
expect(
|
72
|
-
expect(
|
74
|
+
expect(first).to have_received(:get).with('/some/path', query: { query: 1 }).once
|
75
|
+
expect(second).to have_received(:get).with('/some/path', query: { query: 1 }).once
|
76
|
+
expect(third).to have_received(:get).with('/some/path', query: { query: 1 }).once
|
73
77
|
end
|
74
78
|
end
|
75
79
|
end
|
@@ -79,12 +83,16 @@ module Cottus
|
|
79
83
|
described_class.new(connections)
|
80
84
|
end
|
81
85
|
|
82
|
-
let :
|
86
|
+
let :first do
|
83
87
|
double(:http, get: nil)
|
84
88
|
end
|
85
89
|
|
86
|
-
let :
|
87
|
-
|
90
|
+
let :second do
|
91
|
+
double(:http, get: nil)
|
92
|
+
end
|
93
|
+
|
94
|
+
let :third do
|
95
|
+
double(:http, get: nil)
|
88
96
|
end
|
89
97
|
|
90
98
|
describe '#execute' do
|
@@ -96,31 +104,31 @@ module Cottus
|
|
96
104
|
[Timeout::Error, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET].each do |error|
|
97
105
|
context "when #{error} is raised" do
|
98
106
|
context 'with a single host' do
|
99
|
-
let :
|
100
|
-
[
|
107
|
+
let :connections do
|
108
|
+
[first]
|
101
109
|
end
|
102
110
|
|
103
111
|
it 'gives up' do
|
104
|
-
|
112
|
+
connections.each { |conn| conn.stub(:get).with('/some/path', {}).and_raise(error) }
|
105
113
|
|
106
114
|
expect { strategy.execute(:get, '/some/path') }.to raise_error(error)
|
107
115
|
end
|
108
116
|
end
|
109
117
|
|
110
118
|
context 'with several hosts' do
|
111
|
-
let :
|
112
|
-
[
|
119
|
+
let :connections do
|
120
|
+
[first, second, third]
|
113
121
|
end
|
114
122
|
|
115
123
|
it 'attempts to use each host until one succeeds' do
|
116
|
-
[
|
124
|
+
[first, second].each { |conn| conn.stub(:get).with('/some/path', {}).and_raise(error) }
|
117
125
|
|
118
126
|
strategy.execute(:get, '/some/path')
|
119
|
-
expect(
|
127
|
+
expect(third).to have_received(:get).with('/some/path', {})
|
120
128
|
end
|
121
129
|
|
122
130
|
it 'gives up after trying all hosts' do
|
123
|
-
|
131
|
+
connections.each { |conn| conn.stub(:get).with('/some/path', {}).and_raise(error) }
|
124
132
|
|
125
133
|
expect { strategy.execute(:get, '/some/path') }.to raise_error(error)
|
126
134
|
end
|
@@ -136,12 +144,16 @@ module Cottus
|
|
136
144
|
described_class.new(connections, timeouts: [0, 0, 0])
|
137
145
|
end
|
138
146
|
|
139
|
-
let :
|
147
|
+
let :first do
|
140
148
|
double(:http, get: nil)
|
141
149
|
end
|
142
150
|
|
143
|
-
let :
|
144
|
-
|
151
|
+
let :second do
|
152
|
+
double(:http, get: nil)
|
153
|
+
end
|
154
|
+
|
155
|
+
let :third do
|
156
|
+
double(:http, get: nil)
|
145
157
|
end
|
146
158
|
|
147
159
|
describe '#execute' do
|
@@ -153,20 +165,20 @@ module Cottus
|
|
153
165
|
[Timeout::Error, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET].each do |error|
|
154
166
|
context "when #{error} is raised" do
|
155
167
|
context 'with a single host' do
|
156
|
-
let :
|
157
|
-
[
|
168
|
+
let :connections do
|
169
|
+
[first]
|
158
170
|
end
|
159
171
|
|
160
172
|
it 'uses the single host for three consecutive exceptions' do
|
161
|
-
expect(
|
162
|
-
expect(
|
173
|
+
expect(first).to receive(:get).with('/some/path', {}).exactly(3).times.and_raise(error)
|
174
|
+
expect(first).to receive(:get).with('/some/path', {}).once
|
163
175
|
expect(strategy).to receive(:sleep).with(0).exactly(3).times
|
164
176
|
|
165
177
|
strategy.execute(:get, '/some/path')
|
166
178
|
end
|
167
179
|
|
168
180
|
it 'gives up after three retries' do
|
169
|
-
expect(
|
181
|
+
expect(first).to receive(:get).with('/some/path', {}).exactly(4).times.and_raise(error)
|
170
182
|
expect(strategy).to receive(:sleep).with(0).exactly(3).times
|
171
183
|
|
172
184
|
expect { strategy.execute(:get, '/some/path') }.to raise_error(error)
|
@@ -174,21 +186,21 @@ module Cottus
|
|
174
186
|
end
|
175
187
|
|
176
188
|
context 'with several hosts' do
|
177
|
-
let :
|
178
|
-
[
|
189
|
+
let :connections do
|
190
|
+
[first, second, third]
|
179
191
|
end
|
180
192
|
|
181
193
|
it 'uses the same host for three consecutive exceptions' do
|
182
|
-
expect(
|
183
|
-
expect(
|
194
|
+
expect(first).to receive(:get).with('/some/path', {}).exactly(3).times.and_raise(error)
|
195
|
+
expect(first).to receive(:get).with('/some/path', {}).once
|
184
196
|
expect(strategy).to receive(:sleep).with(0).exactly(3).times
|
185
197
|
|
186
198
|
strategy.execute(:get, '/some/path')
|
187
199
|
end
|
188
200
|
|
189
201
|
it 'switches host after three retries' do
|
190
|
-
expect(
|
191
|
-
expect(
|
202
|
+
expect(first).to receive(:get).with('/some/path', {}).exactly(4).times.and_raise(error)
|
203
|
+
expect(second).to receive(:get).with('/some/path', {}).once
|
192
204
|
expect(strategy).to receive(:sleep).with(0).exactly(3).times
|
193
205
|
|
194
206
|
strategy.execute(:get, '/some/path')
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cottus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mathias Söderberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: excon
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - '>='
|