pause 0.1.2 → 0.2.0

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: f681f5880476f055a00a8131eb6977aad96a0ba5
4
- data.tar.gz: 282371534a8dcdb32dac098f479ee5df62433e85
3
+ metadata.gz: bda062b9f3c87e7079efe48798f512afc22cfad8
4
+ data.tar.gz: afcdfb3a35303d0a6dede0964e1d30c275553e50
5
5
  SHA512:
6
- metadata.gz: f9eb4591aa2533a7ea2759e7cb9c929de36ff32b75160701edc436d7ca12ca05dd91f6c72e9b09a4d5b9dfb3a3fcc234a9edd00860b6a6f2d3f60ff1c321ff0c
7
- data.tar.gz: 397807c6f15cd909985b943f3b4b616484c9934025ed27124bed1f76728e0a7ab7e6721dd226135c56352b6b640786be6130210a0f34592d521c5bdf14a33023
6
+ metadata.gz: 8d195486f30a502ef7fee7c77bbd0471f3eeea1622d207d81d4719126e94d4c5d3c8016176c9ff2a40f51f8ba947fda712b629eba702cce8b5819bec6430364f
7
+ data.tar.gz: c8af42cebe5703e60a2d484984f7ba1294d58ecdb9621911ebb02aca9ec66f2657cd0f3372f50c7d7cc8609e3ee92be91df778c2dcdde150760f80abec32833f
data/README.md CHANGED
@@ -198,6 +198,30 @@ action.ok?
198
198
  # => true
199
199
  ```
200
200
 
201
+ ## Using Pause with Twemproxy
202
+
203
+ Pause can be used with Twemproxy to shard its data among multiple redis instances. When doing so,
204
+ the `hash_tag` configuration in Twemproxy should be set to `"||"`. In addition, the `sharded` Pause
205
+ configuration option should be set to true.
206
+
207
+ When sharding is used, the Redis adapter used by Pause ignores the `redis_db`, which is not supported.
208
+
209
+ ```ruby
210
+ Pause.configure do |config|
211
+ config.redis_host = "127.0.0.1"
212
+ config.redis_port = 6379
213
+ config.resolution = 600 # aggregate all events into 10 minute blocks
214
+ config.history = 86400 # discard all events older than 1 day
215
+ config.sharded = true
216
+ end
217
+ ```
218
+
219
+ With this configuration, any Pause operation that we know is not supported by Twemproxy will raise
220
+ `Pause::Redis::OperationNotSupported`. For instance, when sharding we are unable to get a list of all
221
+ tracked identifiers.
222
+
223
+ The action block list is implemented as a sorted set, so it should still be usable when sharding.
224
+
201
225
  ## Contributing
202
226
 
203
227
  Want to make it better? Cool. Here's how:
@@ -14,7 +14,7 @@ module Pause
14
14
  # end
15
15
  #
16
16
  def scope
17
- raise "Should implement scope. (Ex: ipn:follow)"
17
+ raise 'Should implement scope. (Ex: ipn:follow)'
18
18
  end
19
19
 
20
20
  def self.scope(scope_identifier = nil)
@@ -57,6 +57,10 @@ module Pause
57
57
  @checks = period_checks
58
58
  end
59
59
 
60
+ def block_for(ttl)
61
+ adapter.rate_limit!(scope, identifier, ttl)
62
+ end
63
+
60
64
  def increment!(count = 1, timestamp = Time.now.to_i)
61
65
  adapter.increment(scope, identifier, timestamp, count)
62
66
  end
@@ -4,7 +4,14 @@ module Pause
4
4
  class Analyzer
5
5
  include Pause::Helper::Timing
6
6
 
7
+ # #check(action)
8
+ #
9
+ # @param action [Pause::Action]
10
+ # @return [nil] everything is fine
11
+ # @return [false] this action is already blocked
12
+ # @return [Pause::RateLimitedEvent] the action was blocked as a result of this check
7
13
  def check(action)
14
+ return false if adapter.rate_limited?(action.scope, action.identifier)
8
15
  timestamp = period_marker(Pause.config.resolution, Time.now.to_i)
9
16
  set = adapter.key_history(action.scope, action.identifier)
10
17
  action.checks.each do |period_check|
@@ -8,7 +8,7 @@ module Pause
8
8
  end
9
9
 
10
10
  def redis_host
11
- @redis_host || "127.0.0.1"
11
+ @redis_host || '127.0.0.1'
12
12
  end
13
13
 
14
14
  def redis_port
@@ -28,7 +28,7 @@ module Pause
28
28
  end
29
29
 
30
30
  def sharded
31
- @sharded || false
31
+ !!@sharded
32
32
  end
33
33
  end
34
34
  end
@@ -98,7 +98,7 @@ module Pause
98
98
  end
99
99
 
100
100
  def tracked_scope(scope)
101
- ["i", scope].join(':')
101
+ ['i', scope].join(':')
102
102
  end
103
103
 
104
104
  def tracked_key(scope, identifier)
@@ -28,7 +28,7 @@ module Pause
28
28
  end
29
29
 
30
30
  def keys(_key_scope)
31
- raise OperationNotSupported.new("Can not be executed when Pause is configured in sharded mode")
31
+ raise OperationNotSupported.new('Can not be executed when Pause is configured in sharded mode')
32
32
  end
33
33
  end
34
34
  end
@@ -1,3 +1,3 @@
1
1
  module Pause
2
- VERSION = "0.1.2"
2
+ VERSION = '0.2.0'
3
3
  end
@@ -5,7 +5,7 @@ describe Pause::Action do
5
5
  include Pause::Helper::Timing
6
6
 
7
7
  class MyNotification < Pause::Action
8
- scope "ipn:follow"
8
+ scope 'ipn:follow'
9
9
  check period_seconds: 20, max_allowed: 5, block_ttl: 40
10
10
  check period_seconds: 40, max_allowed: 7, block_ttl: 40
11
11
  end
@@ -13,19 +13,20 @@ describe Pause::Action do
13
13
  let(:resolution) { 10 }
14
14
  let(:history) { 60 }
15
15
  let(:configuration) { Pause::Configuration.new }
16
+ let(:adapter) { Pause::Redis::Adapter.new(Pause.config) }
16
17
 
17
18
  before do
18
19
  allow(Pause).to receive(:config).and_return(configuration)
19
20
  allow(Pause.config).to receive(:resolution).and_return(resolution)
20
21
  allow(Pause.config).to receive(:history).and_return(history)
21
- allow(Pause).to receive(:adapter).and_return(Pause::Redis::Adapter.new(Pause.config))
22
+ allow(Pause).to receive(:adapter).and_return(adapter)
22
23
  end
23
24
 
24
- let(:action) { MyNotification.new("1237612") }
25
- let(:other_action) { MyNotification.new("1237613") }
25
+ let(:action) { MyNotification.new('1237612') }
26
+ let(:other_action) { MyNotification.new('1237613') }
26
27
 
27
- describe "#increment!" do
28
- it "should increment" do
28
+ describe '#increment!' do
29
+ it 'should increment' do
29
30
  time = Time.now
30
31
  Timecop.freeze time do
31
32
  expect(Pause.adapter).to receive(:increment).with(action.scope, '1237612', time.to_i, 1)
@@ -34,8 +35,8 @@ describe Pause::Action do
34
35
  end
35
36
  end
36
37
 
37
- describe "#ok?" do
38
- it "should successfully return if the action is blocked or not" do
38
+ describe '#ok?' do
39
+ it 'should successfully return if the action is blocked or not' do
39
40
  time = Time.now
40
41
  Timecop.freeze time do
41
42
  4.times do
@@ -47,7 +48,7 @@ describe Pause::Action do
47
48
  end
48
49
  end
49
50
 
50
- it "should successfully consider different period checks" do
51
+ it 'should successfully consider different period checks' do
51
52
  time = Time.parse('Sept 22, 11:34:00')
52
53
 
53
54
  Timecop.freeze time - 30 do
@@ -66,7 +67,7 @@ describe Pause::Action do
66
67
  end
67
68
  end
68
69
 
69
- it "should return false and silently fail if redis is not available" do
70
+ it 'should return false and silently fail if redis is not available' do
70
71
  allow(Pause::Logger).to receive(:fatal)
71
72
  allow_any_instance_of(Redis).to receive(:zrange).and_raise Redis::CannotConnectError
72
73
  time = period_marker(resolution, Time.now.to_i)
@@ -77,15 +78,15 @@ describe Pause::Action do
77
78
  end
78
79
  end
79
80
 
80
- describe "#analyze" do
81
- context "action should not be rate limited" do
82
- it "returns nil" do
81
+ describe '#analyze' do
82
+ context 'action should not be rate limited' do
83
+ it 'returns nil' do
83
84
  expect(action.analyze).to be nil
84
85
  end
85
86
  end
86
87
 
87
- context "action should be rate limited" do
88
- it "returns a RateLimitedEvent object" do
88
+ context 'action should be rate limited' do
89
+ it 'returns a RateLimitedEvent object' do
89
90
  time = Time.now
90
91
  rate_limit = nil
91
92
 
@@ -105,8 +106,8 @@ describe Pause::Action do
105
106
  end
106
107
  end
107
108
 
108
- describe ".tracked_identifiers" do
109
- it "should return all the identifiers tracked (but not blocked) so far" do
109
+ describe '.tracked_identifiers' do
110
+ it 'should return all the identifiers tracked (but not blocked) so far' do
110
111
  action.increment!
111
112
  other_action.increment!
112
113
 
@@ -118,8 +119,8 @@ describe Pause::Action do
118
119
  end
119
120
  end
120
121
 
121
- describe ".rate_limited_identifiers" do
122
- it "should return all the identifiers blocked" do
122
+ describe '.rate_limited_identifiers' do
123
+ it 'should return all the identifiers blocked' do
123
124
  action.increment!(100, Time.now.to_i)
124
125
  other_action.increment!(100, Time.now.to_i)
125
126
 
@@ -131,8 +132,8 @@ describe Pause::Action do
131
132
  end
132
133
  end
133
134
 
134
- describe ".unblock_all" do
135
- it "should unblock all the identifiers for a scope" do
135
+ describe '.unblock_all' do
136
+ it 'should unblock all the identifiers for a scope' do
136
137
  10.times { action.increment! }
137
138
  other_action.increment!
138
139
 
@@ -149,7 +150,7 @@ describe Pause::Action do
149
150
  end
150
151
  end
151
152
 
152
- describe "#unblock" do
153
+ describe '#unblock' do
153
154
  it 'unblocks the specified id' do
154
155
  10.times { action.increment! }
155
156
 
@@ -160,9 +161,17 @@ describe Pause::Action do
160
161
  expect(action.ok?).to be true
161
162
  end
162
163
  end
164
+
165
+ describe '#block_for' do
166
+ it 'blocks the IP for N seconds' do
167
+ expect(adapter).to receive(:rate_limit!).with(action.scope, action.identifier, 10).and_call_original
168
+ action.block_for(10)
169
+ expect(action.ok?).to be false
170
+ end
171
+ end
163
172
  end
164
173
 
165
- describe Pause::Action, ".check" do
174
+ describe Pause::Action, '.check' do
166
175
  class ActionWithCheck < Pause::Action
167
176
  check 100, 150, 200
168
177
  end
@@ -177,50 +186,50 @@ describe Pause::Action, ".check" do
177
186
  check period_seconds: 50, block_ttl: 60, max_allowed: 100
178
187
  end
179
188
 
180
- it "should define a period check on new instances" do
181
- expect(ActionWithCheck.new("id").checks).to eq([
189
+ it 'should define a period check on new instances' do
190
+ expect(ActionWithCheck.new('id').checks).to eq([
182
191
  Pause::PeriodCheck.new(100, 150, 200)
183
192
  ])
184
193
  end
185
194
 
186
- it "should define a period check on new instances" do
187
- expect(ActionWithMultipleChecks.new("id").checks).to eq([
195
+ it 'should define a period check on new instances' do
196
+ expect(ActionWithMultipleChecks.new('id').checks).to eq([
188
197
  Pause::PeriodCheck.new(100, 150, 200),
189
198
  Pause::PeriodCheck.new(200, 150, 200),
190
199
  Pause::PeriodCheck.new(300, 150, 200)
191
200
  ])
192
201
  end
193
202
 
194
- it "should accept hash arguments" do
195
- expect(ActionWithHashChecks.new("id").checks).to eq([
203
+ it 'should accept hash arguments' do
204
+ expect(ActionWithHashChecks.new('id').checks).to eq([
196
205
  Pause::PeriodCheck.new(50, 100, 60)
197
206
  ])
198
207
  end
199
208
 
200
209
  end
201
210
 
202
- describe Pause::Action, ".scope" do
211
+ describe Pause::Action, '.scope' do
203
212
  class UndefinedScopeAction < Pause::Action
204
213
  end
205
214
 
206
- it "should raise if scope is not defined" do
215
+ it 'should raise if scope is not defined' do
207
216
  expect {
208
- UndefinedScopeAction.new("1.2.3.4").scope
209
- }.to raise_error("Should implement scope. (Ex: ipn:follow)")
217
+ UndefinedScopeAction.new('1.2.3.4').scope
218
+ }.to raise_error('Should implement scope. (Ex: ipn:follow)')
210
219
  end
211
220
 
212
221
  class DefinedScopeAction < Pause::Action
213
- scope "my:scope"
222
+ scope 'my:scope'
214
223
  end
215
224
 
216
- it "should set scope on class" do
217
- expect(DefinedScopeAction.new("1.2.3.4").scope).to eq("my:scope")
225
+ it 'should set scope on class' do
226
+ expect(DefinedScopeAction.new('1.2.3.4').scope).to eq('my:scope')
218
227
  end
219
228
  end
220
229
 
221
- describe Pause::Action, "enabled/disabled states" do
230
+ describe Pause::Action, 'enabled/disabled states' do
222
231
  class BlockedAction < Pause::Action
223
- scope "blocked"
232
+ scope 'blocked'
224
233
  check 10, 0, 10
225
234
  end
226
235
 
@@ -233,27 +242,27 @@ describe Pause::Action, "enabled/disabled states" do
233
242
 
234
243
  let(:action) { BlockedAction }
235
244
 
236
- describe "#disable" do
245
+ describe '#disable' do
237
246
  before do
238
247
  expect(action).to be_enabled
239
248
  expect(action).to_not be_disabled
240
249
  action.disable
241
250
  end
242
251
 
243
- it "disables the action" do
252
+ it 'disables the action' do
244
253
  expect(action).to be_disabled
245
254
  expect(action).to_not be_enabled
246
255
  end
247
256
  end
248
257
 
249
- describe "#enable" do
258
+ describe '#enable' do
250
259
  before do
251
260
  action.disable
252
261
  expect(action).to_not be_enabled
253
262
  action.enable
254
263
  end
255
264
 
256
- it "enables the action" do
265
+ it 'enables the action' do
257
266
  expect(action).to be_enabled
258
267
  expect(action).to_not be_disabled
259
268
  end
@@ -5,7 +5,7 @@ describe Pause::Analyzer do
5
5
  include Pause::Helper::Timing
6
6
 
7
7
  class FollowPushNotification < Pause::Action
8
- scope "ipn:follow"
8
+ scope 'ipn:follow'
9
9
  check 20, 5, 12
10
10
  check 40, 7, 12
11
11
  end
@@ -13,19 +13,20 @@ describe Pause::Analyzer do
13
13
  let(:resolution) { 10 }
14
14
  let(:history) { 60 }
15
15
  let(:configuration) { Pause::Configuration.new }
16
+ let(:adapter) { Pause::Redis::Adapter.new(configuration) }
16
17
 
17
18
  before do
18
19
  allow(Pause).to receive(:config).and_return(configuration)
19
20
  allow(Pause.config).to receive(:resolution).and_return(resolution)
20
21
  allow(Pause.config).to receive(:history).and_return(history)
22
+ allow(Pause).to receive(:adapter).and_return(adapter)
21
23
  end
22
24
 
23
25
  let(:analyzer) { Pause.analyzer }
24
- let(:adapter) { Pause.adapter }
25
- let(:action) { FollowPushNotification.new("1243123") }
26
+ let(:action) { FollowPushNotification.new('1243123') }
26
27
 
27
- describe "#analyze" do
28
- it "checks and blocks if max_allowed is reached" do
28
+ describe '#analyze' do
29
+ it 'checks and blocks if max_allowed is reached' do
29
30
  time = Time.now
30
31
  expect(adapter).to receive(:rate_limit!).once.with(action.scope, '1243123', 12)
31
32
  Timecop.freeze time do
@@ -37,12 +38,12 @@ describe Pause::Analyzer do
37
38
  end
38
39
  end
39
40
 
40
- describe "#check" do
41
- it "should return nil if action is NOT blocked" do
41
+ describe '#check' do
42
+ it 'should return nil if action is NOT blocked' do
42
43
  expect(analyzer.check(action)).to be nil
43
44
  end
44
45
 
45
- it "should return blocked action if action is blocked" do
46
+ it 'should return blocked action if action is blocked' do
46
47
  Timecop.freeze Time.now do
47
48
  5.times do
48
49
  action.increment!
@@ -1,36 +1,39 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Pause::Configuration, "#configure" do
3
+ describe Pause::Configuration, '#configure' do
4
4
 
5
5
  subject { Pause::Configuration.new }
6
6
 
7
- it "should allow configuration via block" do
7
+ it 'should allow configuration via block' do
8
8
  subject.configure do |c|
9
- c.redis_host = "128.23.12.8"
10
- c.redis_port = "2134"
11
- c.redis_db = "13"
9
+ c.redis_host = '128.23.12.8'
10
+ c.redis_port = '2134'
11
+ c.redis_db = '13'
12
12
 
13
13
  c.resolution = 5000
14
14
  c.history = 6000
15
+ c.sharded = true
15
16
  end
16
17
 
17
- expect(subject.redis_host).to eq("128.23.12.8")
18
+ expect(subject.redis_host).to eq('128.23.12.8')
18
19
  expect(subject.redis_port).to eq(2134)
19
- expect(subject.redis_db).to eq("13")
20
+ expect(subject.redis_db).to eq('13')
20
21
 
21
22
  expect(subject.resolution).to eq(5000)
22
23
  expect(subject.history).to eq(6000)
24
+ expect(subject.sharded).to be true
23
25
  end
24
26
 
25
- it "should provide redis defaults" do
27
+ it 'should provide redis defaults' do
26
28
  subject.configure do |config|
27
29
  # do nothing
28
30
  end
29
31
 
30
- expect(subject.redis_host).to eq("127.0.0.1")
32
+ expect(subject.redis_host).to eq('127.0.0.1')
31
33
  expect(subject.redis_port).to eq(6379)
32
- expect(subject.redis_db).to eq("1")
34
+ expect(subject.redis_db).to eq('1')
33
35
  expect(subject.resolution).to eq(600) # 10 minutes
34
36
  expect(subject.history).to eq(86400) # one day
37
+ expect(subject.sharded).to be false
35
38
  end
36
39
  end
@@ -19,11 +19,11 @@ describe Pause::Redis::Adapter do
19
19
  let(:redis_conn) { adapter.send(:redis) }
20
20
 
21
21
  describe '#increment' do
22
- let(:scope) { "blah" }
23
- let(:identifier) { "213213" }
24
- let(:tracked_key) { "i:blah:|213213|"}
22
+ let(:scope) { 'blah' }
23
+ let(:identifier) { '213213' }
24
+ let(:tracked_key) { 'i:blah:|213213|' }
25
25
 
26
- it "should add key to a redis set" do
26
+ it 'should add key to a redis set' do
27
27
  adapter.increment(scope, identifier, Time.now.to_i)
28
28
  set = redis_conn.zrange(tracked_key, 0, -1, :with_scores => true)
29
29
  expect(set).to_not be_empty
@@ -31,7 +31,7 @@ describe Pause::Redis::Adapter do
31
31
  expect(set[0].size).to eql(2)
32
32
  end
33
33
 
34
- it "should remove old key from a redis set" do
34
+ it 'should remove old key from a redis set' do
35
35
  time = Time.now
36
36
  expect(redis_conn).to receive(:zrem).with(tracked_key, [adapter.period_marker(resolution, time)])
37
37
 
@@ -44,7 +44,7 @@ describe Pause::Redis::Adapter do
44
44
  end
45
45
  end
46
46
 
47
- it "sets expiry on key" do
47
+ it 'sets expiry on key' do
48
48
  expect(redis_conn).to receive(:expire).with(tracked_key, history)
49
49
  adapter.increment(scope, identifier, Time.now.to_i)
50
50
  end
@@ -73,41 +73,41 @@ describe Pause::Redis::Adapter do
73
73
  end
74
74
  end
75
75
 
76
- describe "#rate_limit!" do
76
+ describe '#rate_limit!' do
77
77
  end
78
78
 
79
- describe "#rate_limited?" do
79
+ describe '#rate_limited?' do
80
80
  let(:scope) { 'ipn:follow' }
81
81
  let(:identifier) { '123461234' }
82
82
  let(:blocked_key) { "b:#{key}" }
83
83
  let(:ttl) { 110000 }
84
84
 
85
- it "should return true if blocked" do
85
+ it 'should return true if blocked' do
86
86
  adapter.rate_limit!(scope, identifier, ttl)
87
87
  expect(adapter.rate_limited?(scope, identifier)).to be true
88
88
  end
89
89
  end
90
90
 
91
- describe "#tracked_key" do
92
- it "prefixes key" do
93
- expect(adapter.send(:tracked_key, "abc", "12345")).to eq("i:abc:|12345|")
91
+ describe '#tracked_key' do
92
+ it 'prefixes key' do
93
+ expect(adapter.send(:tracked_key, 'abc', '12345')).to eq('i:abc:|12345|')
94
94
  end
95
95
  end
96
96
 
97
97
  describe '#enable' do
98
98
  it 'deletes the disabled flag in redis' do
99
- adapter.disable("boom")
100
- expect(adapter.disabled?("boom")).to be true
101
- adapter.enable("boom")
102
- expect(adapter.disabled?("boom")).to be false
99
+ adapter.disable('boom')
100
+ expect(adapter.disabled?('boom')).to be true
101
+ adapter.enable('boom')
102
+ expect(adapter.disabled?('boom')).to be false
103
103
  end
104
104
  end
105
105
 
106
106
  describe '#disable' do
107
107
  it 'sets the disabled flag in redis' do
108
- expect(adapter.enabled?("boom")).to be true
109
- adapter.disable("boom")
110
- expect(adapter.enabled?("boom")).to be false
108
+ expect(adapter.enabled?('boom')).to be true
109
+ adapter.disable('boom')
110
+ expect(adapter.enabled?('boom')).to be false
111
111
  end
112
112
  end
113
113
 
@@ -124,7 +124,7 @@ describe Pause::Redis::Adapter do
124
124
  let(:blocked_key) { "b:|#{scope}|" }
125
125
  let(:ttl) { 110000 }
126
126
 
127
- it "saves ip to redis with expiration" do
127
+ it 'saves ip to redis with expiration' do
128
128
  time = Time.now
129
129
  Timecop.freeze time do
130
130
  adapter.rate_limit!(scope, identifier, ttl)
@@ -21,8 +21,4 @@ RSpec.configure do |config|
21
21
  # the seed, which is printed after each run.
22
22
  # --seed 1234
23
23
  config.order = 'random'
24
-
25
- config.before :each do
26
- Redis.new.flushall
27
- end
28
24
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pause
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Atasay Gokkaya
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-09-23 00:00:00.000000000 Z
14
+ date: 2015-11-07 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: redis
@@ -85,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
85
  version: '0'
86
86
  requirements: []
87
87
  rubyforge_project:
88
- rubygems_version: 2.2.3
88
+ rubygems_version: 2.2.2
89
89
  signing_key:
90
90
  specification_version: 4
91
91
  summary: RReal time rate limiting for multi-process ruby environments based on Redis