pause 0.1.2 → 0.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 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