pause 0.0.6 → 0.1.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: cebe59883ec6e4803896b89e61d88c53f43902cc
4
- data.tar.gz: 86067516015b359cbe2becb8d2e5b71748484f70
3
+ metadata.gz: b9d25d869302d10074000e45ae903a1d28d66ccc
4
+ data.tar.gz: d3bcfbce3b73826e20481aa28f6a1a70dc61f172
5
5
  SHA512:
6
- metadata.gz: 3538a2c610971934961be482f4531e43a1b614c4d48786484592f4e9c170ef5e4694055d1e8efb80839c62d636c93f997907fd5c9ed1df181adcf8bb6711d361
7
- data.tar.gz: bb3921bf9935e98176a073ac2dabcf43c1786c75ce5e532b04497dba4a8dff039d28ed9a1f88b12fd9a5da6052259e762e1cba1a66bd87bf7288f7d771021e42
6
+ metadata.gz: 812f85e93696fb48483f3d9096a9a6406509361a5145e3cd66614b57bb83e879ec4aa3fd749876a842966a2dd73a54344b0ea957d8e75ffd0bb0653c72aa9670
7
+ data.tar.gz: 29d51c794c8776748fa282ce63c308db83cc6092cbc6f0e407bb37b76140fa9fcc7bcd34847ecbc290f9e5f1b8e022b91e2f3ad17f700dfb5802c7aadf5e867c
data/.travis.yml CHANGED
@@ -5,6 +5,6 @@ script: "bundle exec rspec"
5
5
  notifications:
6
6
  email:
7
7
  recipients:
8
- - dev@wanelo.com
8
+ - dev-info@wanelo.com
9
9
  on_success: never
10
10
  on_failure: always
data/Gemfile CHANGED
@@ -5,3 +5,11 @@ source 'https://rubygems.org'
5
5
 
6
6
  # Specify your gem's dependencies in pause.gemspec
7
7
  gemspec
8
+
9
+ gem 'fakeredis'
10
+ gem 'guard-rspec'
11
+ gem 'pry-nav'
12
+ gem 'rake'
13
+ gem 'rb-fsevent'
14
+ gem 'rspec'
15
+ gem 'timecop'
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/lib/pause.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'redis'
2
- require "pause/version"
3
- require "pause/configuration"
4
- require "pause/action"
5
- require "pause/analyzer"
6
- require "pause/redis/adapter"
2
+ require 'pause/version'
3
+ require 'pause/configuration'
4
+ require 'pause/action'
5
+ require 'pause/analyzer'
6
+ require 'pause/logger'
7
+ require 'pause/redis/adapter'
8
+ require 'pause/redis/sharded_adapter'
7
9
  require 'pause/rate_limited_event'
8
10
 
9
11
  module Pause
@@ -25,7 +27,13 @@ module Pause
25
27
  end
26
28
 
27
29
  def adapter
28
- @adapter ||= Pause::Redis::Adapter.new(config)
30
+ @adapter ||= config.sharded ?
31
+ Pause::Redis::ShardedAdapter.new(config) :
32
+ Pause::Redis::Adapter.new(config)
33
+ end
34
+
35
+ def adapter=(adapter)
36
+ @adapter = adapter
29
37
  end
30
38
 
31
39
  def configure(&block)
data/lib/pause/action.rb CHANGED
@@ -58,7 +58,7 @@ module Pause
58
58
  end
59
59
 
60
60
  def increment!(count = 1, timestamp = Time.now.to_i)
61
- adapter.increment(key, timestamp, count)
61
+ adapter.increment(scope, identifier, timestamp, count)
62
62
  end
63
63
 
64
64
  def rate_limited?
@@ -68,7 +68,7 @@ module Pause
68
68
  def ok?
69
69
  Pause.analyzer.check(self).nil?
70
70
  rescue ::Redis::CannotConnectError => e
71
- $stderr.puts "Error connecting to redis: #{e.inspect}"
71
+ Pause::Logger.fatal "Error connecting to redis: #{e.inspect} #{e.message} #{e.backtrace.join("\n")}"
72
72
  false
73
73
  end
74
74
 
@@ -92,10 +92,6 @@ module Pause
92
92
  adapter.delete_rate_limited_key(scope, identifier)
93
93
  end
94
94
 
95
- def key
96
- "#{self.scope}:#{identifier}"
97
- end
98
-
99
95
  # Actions can be globally disabled or re-enabled in a persistent
100
96
  # way.
101
97
  #
@@ -6,14 +6,14 @@ module Pause
6
6
 
7
7
  def check(action)
8
8
  timestamp = period_marker(Pause.config.resolution, Time.now.to_i)
9
- set = adapter.key_history(action.key)
9
+ set = adapter.key_history(action.scope, action.identifier)
10
10
  action.checks.each do |period_check|
11
11
  start_time = timestamp - period_check.period_seconds
12
12
  set.reverse.inject(0) do |sum, element|
13
13
  break if element.ts < start_time
14
14
  sum += element.count
15
15
  if sum >= period_check.max_allowed
16
- adapter.rate_limit!(action.key, period_check.block_ttl)
16
+ adapter.rate_limit!(action.scope, action.identifier, period_check.block_ttl)
17
17
  # Note that Time.now is different from period_marker(resolution, Time.now), which
18
18
  # rounds down to the nearest (resolution) seconds
19
19
  return Pause::RateLimitedEvent.new(action, period_check, sum, Time.now.to_i)
@@ -1,6 +1,6 @@
1
1
  module Pause
2
2
  class Configuration
3
- attr_writer :redis_host, :redis_port, :redis_db, :resolution, :history
3
+ attr_writer :redis_host, :redis_port, :redis_db, :resolution, :history, :sharded
4
4
 
5
5
  def configure
6
6
  yield self
@@ -26,5 +26,9 @@ module Pause
26
26
  def history
27
27
  (@history || 86400).to_i
28
28
  end
29
+
30
+ def sharded
31
+ @sharded || false
32
+ end
29
33
  end
30
34
  end
@@ -0,0 +1,11 @@
1
+ module Pause
2
+ class Logger
3
+ def self.puts message
4
+ STDOUT.puts message
5
+ end
6
+
7
+ def self.fatal message
8
+ STDERR.puts message
9
+ end
10
+ end
11
+ end
@@ -15,8 +15,8 @@ module Pause
15
15
  @history = config.history
16
16
  end
17
17
 
18
- def increment(key, timestamp, count = 1)
19
- k = white_key(key)
18
+ def increment(scope, identifier, timestamp, count = 1)
19
+ k = tracked_key(scope, identifier)
20
20
  redis.multi do |redis|
21
21
  redis.zincrby k, count, period_marker(resolution, timestamp)
22
22
  redis.expire k, history
@@ -29,40 +29,47 @@ module Pause
29
29
  end
30
30
  end
31
31
 
32
- def key_history(key)
33
- extract_set_elements(white_key(key))
32
+ def key_history(scope, identifier)
33
+ extract_set_elements(tracked_key(scope, identifier))
34
34
  end
35
35
 
36
- def rate_limit!(key, block_ttl)
37
- redis.setex(rate_limited_key(key), block_ttl, nil)
36
+ def rate_limit!(scope, identifier, block_ttl)
37
+ timestamp = Time.now.to_i + block_ttl
38
+ redis.zadd rate_limited_list(scope), timestamp, identifier
39
+ expire_block_list(scope)
38
40
  end
39
41
 
40
- def rate_limited?(key)
41
- !!redis.get(rate_limited_key(key))
42
+ def rate_limited?(scope, identifier)
43
+ blocked_until = redis.zscore(rate_limited_list(scope), identifier)
44
+ !!blocked_until && blocked_until > Time.now.to_i
42
45
  end
43
46
 
44
47
  def all_keys(scope)
45
- keys(white_key(scope))
48
+ keys(tracked_scope(scope))
46
49
  end
47
50
 
48
51
  def rate_limited_keys(scope)
49
- keys(rate_limited_key(scope))
52
+ redis.zrangebyscore rate_limited_list(scope), Time.now.to_i, '+inf'
50
53
  end
51
54
 
55
+ # For a scope, delete the entire sorted set that holds the block list.
56
+ # Also delete the original tracking information, so we don't immediately re-block the id
52
57
  def delete_rate_limited_keys(scope)
53
- delete_rate_limited_ids scope, rate_limited_keys(scope)
58
+ delete_tracking_keys(scope, rate_limited_keys(scope))
59
+ redis.del rate_limited_list(scope)
54
60
  end
55
61
 
56
62
  def delete_rate_limited_key(scope, id)
57
- delete_rate_limited_ids scope, [id]
63
+ delete_tracking_keys(scope, [id])
64
+ redis.zrem rate_limited_list(scope), id
58
65
  end
59
66
 
60
67
  def disable(scope)
61
- redis.set("disabled:#{scope}", "1")
68
+ redis.set("internal:|#{scope}|:disabled", "1")
62
69
  end
63
70
 
64
71
  def enable(scope)
65
- redis.del("disabled:#{scope}")
72
+ redis.del("internal:|#{scope}|:disabled")
66
73
  end
67
74
 
68
75
  def disabled?(scope)
@@ -70,15 +77,18 @@ module Pause
70
77
  end
71
78
 
72
79
  def enabled?(scope)
73
- redis.keys("disabled:#{scope}").first.nil?
80
+ redis.get("internal:|#{scope}|:disabled").nil?
81
+ end
82
+
83
+ def expire_block_list(scope)
84
+ redis.zremrangebyscore rate_limited_list(scope), '-inf', Time.now.to_i
74
85
  end
75
86
 
76
87
  private
77
88
 
78
- def delete_rate_limited_ids(scope, ids)
79
- increment_keys = ids.map{ |key| white_key(scope, key) }
80
- rate_limited_keys = ids.map{ |key| rate_limited_key(scope, key) }
81
- redis.del(increment_keys + rate_limited_keys)
89
+ def delete_tracking_keys(scope, ids)
90
+ increment_keys = ids.map{ |key| tracked_key(scope, key) }
91
+ redis.del(increment_keys)
82
92
  end
83
93
 
84
94
  def redis
@@ -87,22 +97,27 @@ module Pause
87
97
  db: Pause.config.redis_db)
88
98
  end
89
99
 
90
- def white_key(scope, key = nil)
91
- ["i", scope, key].compact.join(':')
100
+ def tracked_scope(scope)
101
+ ["i", scope].join(':')
102
+ end
103
+
104
+ def tracked_key(scope, identifier)
105
+ id = "|#{identifier}|"
106
+ [tracked_scope(scope), id].join(':')
92
107
  end
93
108
 
94
- def rate_limited_key(scope, key = nil)
95
- ["b", scope, key].compact.join(':')
109
+ def rate_limited_list(scope)
110
+ "b:|#{scope}|"
96
111
  end
97
112
 
98
113
  def keys(key_scope)
99
114
  redis.keys("#{key_scope}:*").map do |key|
100
- key.gsub(/^#{key_scope}:/, "")
115
+ key.gsub(/^#{key_scope}:/, "").tr('|','')
101
116
  end
102
117
  end
103
118
 
104
119
  def extract_set_elements(key)
105
- (redis.zrange key, 0, -1, :with_scores => true).map do |slice|
120
+ (redis.zrange key, 0, -1, with_scores: true).map do |slice|
106
121
  Pause::SetElement.new(slice[0].to_i, slice[1].to_i)
107
122
  end.sort
108
123
  end
@@ -0,0 +1,17 @@
1
+ module Pause
2
+ module Redis
3
+ class OperationNotSupported < StandardError
4
+ end
5
+
6
+ # This class encapsulates Redis operations used by Pause.
7
+ # Operations that are not possible when data is sharded
8
+ # raise an error.
9
+ class ShardedAdapter < Adapter
10
+ private
11
+
12
+ def keys(_key_scope)
13
+ raise OperationNotSupported.new("Can not be executed when Pause is configured in sharded mode")
14
+ end
15
+ end
16
+ end
17
+ end
data/lib/pause/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pause
2
- VERSION = "0.0.6"
2
+ VERSION = "0.1.0"
3
3
  end
data/pause.gemspec CHANGED
@@ -18,10 +18,4 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
 
20
20
  gem.add_dependency 'redis'
21
-
22
- gem.add_development_dependency 'rspec'
23
- gem.add_development_dependency 'fakeredis'
24
- gem.add_development_dependency 'timecop'
25
- gem.add_development_dependency 'guard-rspec'
26
- gem.add_development_dependency 'rb-fsevent'
27
21
  end
@@ -15,9 +15,10 @@ describe Pause::Action do
15
15
  let(:configuration) { Pause::Configuration.new }
16
16
 
17
17
  before do
18
- Pause.stub(:config).and_return(configuration)
19
- Pause.config.stub(:resolution).and_return(resolution)
20
- Pause.config.stub(:history).and_return(history)
18
+ allow(Pause).to receive(:config).and_return(configuration)
19
+ allow(Pause.config).to receive(:resolution).and_return(resolution)
20
+ allow(Pause.config).to receive(:history).and_return(history)
21
+ allow(Pause).to receive(:adapter).and_return(Pause::Redis::Adapter.new(Pause.config))
21
22
  end
22
23
 
23
24
  let(:action) { MyNotification.new("1237612") }
@@ -27,7 +28,7 @@ describe Pause::Action do
27
28
  it "should increment" do
28
29
  time = Time.now
29
30
  Timecop.freeze time do
30
- Pause.adapter.should_receive(:increment).with(action.key, time.to_i, 1)
31
+ expect(Pause.adapter).to receive(:increment).with(action.scope, '1237612', time.to_i, 1)
31
32
  action.increment!
32
33
  end
33
34
  end
@@ -39,41 +40,47 @@ describe Pause::Action do
39
40
  Timecop.freeze time do
40
41
  4.times do
41
42
  action.increment!
42
- action.ok?.should be_true
43
+ expect(action.ok?).to be true
43
44
  end
44
45
  action.increment!
45
- action.ok?.should be_false
46
+ expect(action.ok?).to be false
46
47
  end
47
48
  end
48
49
 
49
50
  it "should successfully consider different period checks" do
50
- time = period_marker(resolution, Time.now.to_i)
51
-
52
- action.increment! 4, time - 25
53
- action.ok?.should be_true
51
+ time = Time.parse('Sept 22, 11:34:00')
54
52
 
55
- action.increment! 2, time - 3
56
- action.ok?.should be_true
53
+ Timecop.freeze time - 30 do
54
+ action.increment! 4
55
+ expect(action.ok?).to be true
56
+ end
57
57
 
58
- action.increment! 1, time
58
+ Timecop.freeze time do
59
+ action.increment! 2
60
+ expect(action.ok?).to be true
61
+ end
59
62
 
60
- action.ok?.should be_false
63
+ Timecop.freeze time do
64
+ action.increment! 1
65
+ expect(action.ok?).to be false
66
+ end
61
67
  end
62
68
 
63
69
  it "should return false and silently fail if redis is not available" do
64
- Redis.any_instance.stub(:zrange) { raise Redis::CannotConnectError }
70
+ allow(Pause::Logger).to receive(:fatal)
71
+ allow_any_instance_of(Redis).to receive(:zrange).and_raise Redis::CannotConnectError
65
72
  time = period_marker(resolution, Time.now.to_i)
66
73
 
67
74
  action.increment! 4, time - 25
68
75
 
69
- action.ok?.should be_false
76
+ expect(action.ok?).to be false
70
77
  end
71
78
  end
72
79
 
73
80
  describe "#analyze" do
74
81
  context "action should not be rate limited" do
75
82
  it "returns nil" do
76
- action.analyze.should be_nil
83
+ expect(action.analyze).to be nil
77
84
  end
78
85
  end
79
86
 
@@ -89,11 +96,11 @@ describe Pause::Action do
89
96
 
90
97
  expected_rate_limit = Pause::RateLimitedEvent.new(action, action.checks[0], 7, time.to_i)
91
98
 
92
- rate_limit.should be_a(Pause::RateLimitedEvent)
93
- rate_limit.identifier.should == expected_rate_limit.identifier
94
- rate_limit.sum.should == expected_rate_limit.sum
95
- rate_limit.period_check.should == expected_rate_limit.period_check
96
- rate_limit.timestamp.should == expected_rate_limit.timestamp
99
+ expect(rate_limit).to be_a(Pause::RateLimitedEvent)
100
+ expect(rate_limit.identifier).to eq(expected_rate_limit.identifier)
101
+ expect(rate_limit.sum).to eq(expected_rate_limit.sum)
102
+ expect(rate_limit.period_check).to eq(expected_rate_limit.period_check)
103
+ expect(rate_limit.timestamp).to eq(expected_rate_limit.timestamp)
97
104
  end
98
105
  end
99
106
  end
@@ -106,8 +113,8 @@ describe Pause::Action do
106
113
  action.ok?
107
114
  other_action.ok?
108
115
 
109
- MyNotification.tracked_identifiers.should include(action.identifier)
110
- MyNotification.tracked_identifiers.should include(other_action.identifier)
116
+ expect(MyNotification.tracked_identifiers).to include(action.identifier)
117
+ expect(MyNotification.tracked_identifiers).to include(other_action.identifier)
111
118
  end
112
119
  end
113
120
 
@@ -119,8 +126,8 @@ describe Pause::Action do
119
126
  action.ok?
120
127
  other_action.ok?
121
128
 
122
- MyNotification.rate_limited_identifiers.should include(action.identifier)
123
- MyNotification.rate_limited_identifiers.should include(other_action.identifier)
129
+ expect(MyNotification.rate_limited_identifiers).to include(action.identifier)
130
+ expect(MyNotification.rate_limited_identifiers).to include(other_action.identifier)
124
131
  end
125
132
  end
126
133
 
@@ -132,13 +139,13 @@ describe Pause::Action do
132
139
  action.ok?
133
140
  other_action.ok?
134
141
 
135
- MyNotification.tracked_identifiers.should include(action.identifier, other_action.identifier)
136
- MyNotification.rate_limited_identifiers.should == [action.identifier]
142
+ expect(MyNotification.tracked_identifiers).to include(action.identifier, other_action.identifier)
143
+ expect(MyNotification.rate_limited_identifiers).to eq([action.identifier])
137
144
 
138
145
  MyNotification.unblock_all
139
146
 
140
- MyNotification.rate_limited_identifiers.should be_empty
141
- MyNotification.tracked_identifiers.should == [other_action.identifier]
147
+ expect(MyNotification.rate_limited_identifiers).to be_empty
148
+ expect(MyNotification.tracked_identifiers).to eq([other_action.identifier])
142
149
  end
143
150
  end
144
151
 
@@ -146,11 +153,11 @@ describe Pause::Action do
146
153
  it 'unblocks the specified id' do
147
154
  10.times { action.increment! }
148
155
 
149
- expect(action.ok?).to be_false
156
+ expect(action.ok?).to be false
150
157
 
151
158
  action.unblock
152
159
 
153
- expect(action.ok?).to be_true
160
+ expect(action.ok?).to be true
154
161
  end
155
162
  end
156
163
  end
@@ -171,23 +178,23 @@ describe Pause::Action, ".check" do
171
178
  end
172
179
 
173
180
  it "should define a period check on new instances" do
174
- ActionWithCheck.new("id").checks.should == [
175
- Pause::PeriodCheck.new(100, 150, 200)
176
- ]
181
+ expect(ActionWithCheck.new("id").checks).to eq([
182
+ Pause::PeriodCheck.new(100, 150, 200)
183
+ ])
177
184
  end
178
185
 
179
186
  it "should define a period check on new instances" do
180
- ActionWithMultipleChecks.new("id").checks.should == [
181
- Pause::PeriodCheck.new(100, 150, 200),
182
- Pause::PeriodCheck.new(200, 150, 200),
183
- Pause::PeriodCheck.new(300, 150, 200)
184
- ]
187
+ expect(ActionWithMultipleChecks.new("id").checks).to eq([
188
+ Pause::PeriodCheck.new(100, 150, 200),
189
+ Pause::PeriodCheck.new(200, 150, 200),
190
+ Pause::PeriodCheck.new(300, 150, 200)
191
+ ])
185
192
  end
186
193
 
187
194
  it "should accept hash arguments" do
188
- ActionWithHashChecks.new("id").checks.should == [
189
- Pause::PeriodCheck.new(50, 100, 60)
190
- ]
195
+ expect(ActionWithHashChecks.new("id").checks).to eq([
196
+ Pause::PeriodCheck.new(50, 100, 60)
197
+ ])
191
198
  end
192
199
 
193
200
  end
@@ -197,9 +204,9 @@ describe Pause::Action, ".scope" do
197
204
  end
198
205
 
199
206
  it "should raise if scope is not defined" do
200
- lambda {
207
+ expect {
201
208
  UndefinedScopeAction.new("1.2.3.4").scope
202
- }.should raise_error("Should implement scope. (Ex: ipn:follow)")
209
+ }.to raise_error("Should implement scope. (Ex: ipn:follow)")
203
210
  end
204
211
 
205
212
  class DefinedScopeAction < Pause::Action
@@ -207,7 +214,7 @@ describe Pause::Action, ".scope" do
207
214
  end
208
215
 
209
216
  it "should set scope on class" do
210
- DefinedScopeAction.new("1.2.3.4").scope.should == "my:scope"
217
+ expect(DefinedScopeAction.new("1.2.3.4").scope).to eq("my:scope")
211
218
  end
212
219
  end
213
220
 
@@ -228,27 +235,27 @@ describe Pause::Action, "enabled/disabled states" do
228
235
 
229
236
  describe "#disable" do
230
237
  before do
231
- action.should be_enabled
232
- action.should_not be_disabled
238
+ expect(action).to be_enabled
239
+ expect(action).to_not be_disabled
233
240
  action.disable
234
241
  end
235
242
 
236
243
  it "disables the action" do
237
- action.should be_disabled
238
- action.should_not be_enabled
244
+ expect(action).to be_disabled
245
+ expect(action).to_not be_enabled
239
246
  end
240
247
  end
241
248
 
242
249
  describe "#enable" do
243
250
  before do
244
251
  action.disable
245
- action.should_not be_enabled
252
+ expect(action).to_not be_enabled
246
253
  action.enable
247
254
  end
248
255
 
249
256
  it "enables the action" do
250
- action.should be_enabled
251
- action.should_not be_disabled
257
+ expect(action).to be_enabled
258
+ expect(action).to_not be_disabled
252
259
  end
253
260
  end
254
261
  end
@@ -15,9 +15,9 @@ describe Pause::Analyzer do
15
15
  let(:configuration) { Pause::Configuration.new }
16
16
 
17
17
  before do
18
- Pause.stub(:config).and_return(configuration)
19
- Pause.config.stub(:resolution).and_return(resolution)
20
- Pause.config.stub(:history).and_return(history)
18
+ allow(Pause).to receive(:config).and_return(configuration)
19
+ allow(Pause.config).to receive(:resolution).and_return(resolution)
20
+ allow(Pause.config).to receive(:history).and_return(history)
21
21
  end
22
22
 
23
23
  let(:analyzer) { Pause.analyzer }
@@ -27,7 +27,7 @@ describe Pause::Analyzer do
27
27
  describe "#analyze" do
28
28
  it "checks and blocks if max_allowed is reached" do
29
29
  time = Time.now
30
- adapter.should_receive(:rate_limit!).once.with(action.key, 12)
30
+ expect(adapter).to receive(:rate_limit!).once.with(action.scope, '1243123', 12)
31
31
  Timecop.freeze time do
32
32
  5.times do
33
33
  action.increment!
@@ -39,7 +39,7 @@ describe Pause::Analyzer do
39
39
 
40
40
  describe "#check" do
41
41
  it "should return nil if action is NOT blocked" do
42
- analyzer.check(action).should be_nil
42
+ expect(analyzer.check(action)).to be nil
43
43
  end
44
44
 
45
45
  it "should return blocked action if action is blocked" do
@@ -47,7 +47,7 @@ describe Pause::Analyzer do
47
47
  5.times do
48
48
  action.increment!
49
49
  end
50
- analyzer.check(action).should be_a(Pause::RateLimitedEvent)
50
+ expect(analyzer.check(action)).to be_a(Pause::RateLimitedEvent)
51
51
  end
52
52
  end
53
53
  end
@@ -14,12 +14,12 @@ describe Pause::Configuration, "#configure" do
14
14
  c.history = 6000
15
15
  end
16
16
 
17
- subject.redis_host.should == "128.23.12.8"
18
- subject.redis_port.should == 2134
19
- subject.redis_db.should == "13"
17
+ expect(subject.redis_host).to eq("128.23.12.8")
18
+ expect(subject.redis_port).to eq(2134)
19
+ expect(subject.redis_db).to eq("13")
20
20
 
21
- subject.resolution.should == 5000
22
- subject.history.should == 6000
21
+ expect(subject.resolution).to eq(5000)
22
+ expect(subject.history).to eq(6000)
23
23
  end
24
24
 
25
25
  it "should provide redis defaults" do
@@ -27,10 +27,10 @@ describe Pause::Configuration, "#configure" do
27
27
  # do nothing
28
28
  end
29
29
 
30
- subject.redis_host.should == "127.0.0.1"
31
- subject.redis_port.should == 6379
32
- subject.redis_db.should == "1"
33
- subject.resolution.should == 600 # 10 minutes
34
- subject.history.should == 86400 # one day
30
+ expect(subject.redis_host).to eq("127.0.0.1")
31
+ expect(subject.redis_port).to eq(6379)
32
+ expect(subject.redis_db).to eq("1")
33
+ expect(subject.resolution).to eq(600) # 10 minutes
34
+ expect(subject.history).to eq(86400) # one day
35
35
  end
36
36
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Pause do
4
+ describe 'adapter' do
5
+ let(:configuration) { Pause::Configuration.new }
6
+
7
+ before do
8
+ Pause.adapter = nil
9
+ allow(Pause).to receive(:config).and_return(configuration)
10
+ configuration.configure { |c| c.sharded = sharded }
11
+ end
12
+
13
+ context 'pause is sharded' do
14
+ let(:sharded) { true }
15
+
16
+ it 'is a ShardedAdapter' do
17
+ expect(Pause.adapter).to be_a(Pause::Redis::ShardedAdapter)
18
+ end
19
+ end
20
+
21
+ context 'pause is not sharded' do
22
+ let(:sharded) { false }
23
+
24
+ it 'is an Adapter' do
25
+ expect(Pause.adapter).to be_a(Pause::Redis::Adapter)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -9,9 +9,9 @@ describe Pause::Redis::Adapter do
9
9
  let(:configuration) { Pause::Configuration.new }
10
10
 
11
11
  before do
12
- Pause.stub(:config).and_return(configuration)
13
- Pause.config.stub(:resolution).and_return(resolution)
14
- Pause.config.stub(:history).and_return(history)
12
+ allow(Pause).to receive(:config).and_return(configuration)
13
+ allow(Pause.config).to receive(:resolution).and_return(resolution)
14
+ allow(Pause.config).to receive(:history).and_return(history)
15
15
  redis_conn.flushall
16
16
  end
17
17
 
@@ -19,116 +19,150 @@ describe Pause::Redis::Adapter do
19
19
  let(:redis_conn) { adapter.send(:redis) }
20
20
 
21
21
  describe '#increment' do
22
- let(:key) { "213213" }
22
+ let(:scope) { "blah" }
23
+ let(:identifier) { "213213" }
24
+ let(:tracked_key) { "i:blah:|213213|"}
23
25
 
24
26
  it "should add key to a redis set" do
25
- adapter.increment(key, Time.now.to_i)
26
- set = redis_conn.zrange(adapter.send(:white_key, key), 0, -1, :with_scores => true)
27
- set.should_not be_empty
28
- set.size.should eql(1)
29
- set[0].size.should eql(2)
27
+ adapter.increment(scope, identifier, Time.now.to_i)
28
+ set = redis_conn.zrange(tracked_key, 0, -1, :with_scores => true)
29
+ expect(set).to_not be_empty
30
+ expect(set.size).to eql(1)
31
+ expect(set[0].size).to eql(2)
30
32
  end
31
33
 
32
34
  it "should remove old key from a redis set" do
33
35
  time = Time.now
34
- redis_conn.should_receive(:zrem).with(adapter.send(:white_key, key), [adapter.period_marker(resolution, time)])
36
+ expect(redis_conn).to receive(:zrem).with(tracked_key, [adapter.period_marker(resolution, time)])
35
37
 
36
38
  adapter.time_blocks_to_keep = 1
37
39
  Timecop.freeze time do
38
- adapter.increment(key, Time.now.to_i)
40
+ adapter.increment(scope, identifier, Time.now.to_i)
39
41
  end
40
42
  Timecop.freeze time + (adapter.resolution + 1) do
41
- adapter.increment(key, Time.now.to_i)
43
+ adapter.increment(scope, identifier, Time.now.to_i)
42
44
  end
43
45
  end
44
46
 
45
47
  it "sets expiry on key" do
46
- redis_conn.should_receive(:expire).with(adapter.send(:white_key, key), history)
47
- adapter.increment(key, Time.now.to_i)
48
+ expect(redis_conn).to receive(:expire).with(tracked_key, history)
49
+ adapter.increment(scope, identifier, Time.now.to_i)
48
50
  end
49
51
  end
50
52
 
51
- describe "#block" do
52
- let(:key) { "ipn:follow:123461234" }
53
- let(:blocked_key) { "b:#{key}" }
54
- let(:ttl) { 110000 }
53
+ describe '#expire_block_list' do
54
+ let(:scope) { 'a' }
55
+ let(:expired_identifier) { '123' }
56
+ let(:blocked_identifier) { '124' }
57
+
58
+ it 'clears all entries with score older than now' do
59
+ now = Time.now
60
+
61
+ Timecop.freeze now - 10 do
62
+ adapter.rate_limit!(scope, expired_identifier, 5)
63
+ end
64
+
65
+ Timecop.freeze now - 4 do
66
+ adapter.rate_limit!(scope, blocked_identifier, 5)
67
+ end
55
68
 
56
- it "saves ip to redis with expiration" do
57
- adapter.rate_limit!(key, ttl)
58
- redis_conn.get(blocked_key).should_not be_nil
59
- redis_conn.ttl(blocked_key).should == ttl
69
+ adapter.expire_block_list(scope)
70
+
71
+ expect(redis_conn.zscore('b:|a|', blocked_identifier)).not_to be nil
72
+ expect(redis_conn.zscore('b:|a|', expired_identifier)).to be nil
60
73
  end
61
74
  end
62
75
 
63
- describe "#blocked?" do
64
- let(:key) { "ipn:follow:123461234" }
76
+ describe "#rate_limit!" do
77
+ end
78
+
79
+ describe "#rate_limited?" do
80
+ let(:scope) { 'ipn:follow' }
81
+ let(:identifier) { '123461234' }
65
82
  let(:blocked_key) { "b:#{key}" }
66
83
  let(:ttl) { 110000 }
67
84
 
68
85
  it "should return true if blocked" do
69
- adapter.rate_limit!(key, ttl)
70
- (!!redis_conn.get(blocked_key).should) == adapter.rate_limited?(key)
86
+ adapter.rate_limit!(scope, identifier, ttl)
87
+ expect(adapter.rate_limited?(scope, identifier)).to be true
71
88
  end
72
89
  end
73
90
 
74
- describe "#white_key" do
91
+ describe "#tracked_key" do
75
92
  it "prefixes key" do
76
- adapter.send(:white_key, "abc").should == "i:abc"
93
+ expect(adapter.send(:tracked_key, "abc", "12345")).to eq("i:abc:|12345|")
77
94
  end
78
95
  end
79
96
 
80
97
  describe '#enable' do
81
98
  it 'deletes the disabled flag in redis' do
82
99
  adapter.disable("boom")
83
- expect(adapter.disabled?("boom")).to be_true
100
+ expect(adapter.disabled?("boom")).to be true
84
101
  adapter.enable("boom")
85
- expect(adapter.disabled?("boom")).to be_false
102
+ expect(adapter.disabled?("boom")).to be false
86
103
  end
87
104
  end
88
105
 
89
106
  describe '#disable' do
90
107
  it 'sets the disabled flag in redis' do
91
- expect(adapter.enabled?("boom")).to be_true
108
+ expect(adapter.enabled?("boom")).to be true
92
109
  adapter.disable("boom")
93
- expect(adapter.enabled?("boom")).to be_false
110
+ expect(adapter.enabled?("boom")).to be false
94
111
  end
95
112
  end
96
113
 
97
114
  describe '#rate_limit!' do
98
115
  it 'rate limits a key for a specific ttl' do
99
- expect(adapter.rate_limited?('1')).to be_false
100
- adapter.rate_limit!('1', 10)
101
- expect(adapter.rate_limited?('1')).to be_true
116
+ expect(adapter.rate_limited?('blah', '1')).to be false
117
+ adapter.rate_limit!('blah', '1', 10)
118
+ expect(adapter.rate_limited?('blah', '1')).to be true
119
+ end
120
+
121
+ describe 'redis internals' do
122
+ let(:scope) { 'ipn:follow' }
123
+ let(:identifier) { '1234' }
124
+ let(:blocked_key) { "b:|#{scope}|" }
125
+ let(:ttl) { 110000 }
126
+
127
+ it "saves ip to redis with expiration" do
128
+ time = Time.now
129
+ Timecop.freeze time do
130
+ adapter.rate_limit!(scope, identifier, ttl)
131
+ end
132
+ expect(redis_conn.zscore(blocked_key, identifier)).to_not be nil
133
+ expect(redis_conn.zscore(blocked_key, identifier)).to eq(time.to_i + ttl)
134
+ end
135
+
102
136
  end
103
137
  end
104
138
 
105
139
  describe '#delete_rate_limited_keys' do
106
140
  it 'calls redis del with all keys' do
107
- adapter.rate_limit!('boom:1', 10)
108
- adapter.rate_limit!('boom:2', 10)
141
+ adapter.rate_limit!('boom', '1', 10)
142
+ adapter.rate_limit!('boom', '2', 10)
109
143
 
110
- expect(adapter.rate_limited?('boom:1')).to be_true
111
- expect(adapter.rate_limited?('boom:2')).to be_true
144
+ expect(adapter.rate_limited?('boom', '1')).to be true
145
+ expect(adapter.rate_limited?('boom', '2')).to be true
112
146
 
113
147
  adapter.delete_rate_limited_keys('boom')
114
148
 
115
- expect(adapter.rate_limited?('boom:1')).to be_false
116
- expect(adapter.rate_limited?('boom:2')).to be_false
149
+ expect(adapter.rate_limited?('boom', '1')).to be false
150
+ expect(adapter.rate_limited?('boom', '2')).to be false
117
151
  end
118
152
  end
119
153
 
120
154
  describe '#delete_rate_limit_key' do
121
155
  it 'calls redis del with all keys' do
122
- adapter.rate_limit!('boom:1', 10)
123
- adapter.rate_limit!('boom:2', 10)
156
+ adapter.rate_limit!('boom', '1', 10)
157
+ adapter.rate_limit!('boom', '2', 10)
124
158
 
125
- expect(adapter.rate_limited?('boom:1')).to be_true
126
- expect(adapter.rate_limited?('boom:2')).to be_true
159
+ expect(adapter.rate_limited?('boom', '1')).to be true
160
+ expect(adapter.rate_limited?('boom', '2')).to be true
127
161
 
128
162
  adapter.delete_rate_limited_key('boom', '1')
129
163
 
130
- expect(adapter.rate_limited?('boom:1')).to be_false
131
- expect(adapter.rate_limited?('boom:2')).to be_true
164
+ expect(adapter.rate_limited?('boom', '1')).to be false
165
+ expect(adapter.rate_limited?('boom', '2')).to be true
132
166
  end
133
167
  end
134
168
  end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'date'
3
+ require 'timecop'
4
+
5
+ describe Pause::Redis::ShardedAdapter do
6
+
7
+ let(:resolution) { 10 }
8
+ let(:history) { 60 }
9
+ let(:configuration) { Pause::Configuration.new }
10
+
11
+ before do
12
+ allow(Pause).to receive(:config).and_return(configuration)
13
+ allow(Pause.config).to receive(:resolution).and_return(resolution)
14
+ allow(Pause.config).to receive(:history).and_return(history)
15
+ end
16
+
17
+ let(:adapter) { Pause::Redis::ShardedAdapter.new(Pause.config) }
18
+
19
+ describe '#all_keys' do
20
+ it 'is not supported' do
21
+ expect { adapter.all_keys('cake') }.to raise_error(Pause::Redis::OperationNotSupported)
22
+ end
23
+ end
24
+ end
data/spec/spec_helper.rb CHANGED
@@ -9,10 +9,10 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
9
9
  require 'rubygems'
10
10
  require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
11
11
  require 'pause'
12
+ require 'pry'
12
13
  require 'support/fakeredis'
13
14
 
14
15
  RSpec.configure do |config|
15
- config.treat_symbols_as_metadata_keys_with_true_values = true
16
16
  config.run_all_when_everything_filtered = true
17
17
  config.filter_run :focus
18
18
 
@@ -1,3 +1,2 @@
1
- require 'fakeredis'
2
1
  require 'fakeredis/rspec'
3
2
 
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.0.6
4
+ version: 0.1.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: 2014-05-23 00:00:00.000000000 Z
14
+ date: 2015-09-22 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: redis
@@ -27,76 +27,6 @@ dependencies:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
- - !ruby/object:Gem::Dependency
31
- name: rspec
32
- requirement: !ruby/object:Gem::Requirement
33
- requirements:
34
- - - ">="
35
- - !ruby/object:Gem::Version
36
- version: '0'
37
- type: :development
38
- prerelease: false
39
- version_requirements: !ruby/object:Gem::Requirement
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- version: '0'
44
- - !ruby/object:Gem::Dependency
45
- name: fakeredis
46
- requirement: !ruby/object:Gem::Requirement
47
- requirements:
48
- - - ">="
49
- - !ruby/object:Gem::Version
50
- version: '0'
51
- type: :development
52
- prerelease: false
53
- version_requirements: !ruby/object:Gem::Requirement
54
- requirements:
55
- - - ">="
56
- - !ruby/object:Gem::Version
57
- version: '0'
58
- - !ruby/object:Gem::Dependency
59
- name: timecop
60
- requirement: !ruby/object:Gem::Requirement
61
- requirements:
62
- - - ">="
63
- - !ruby/object:Gem::Version
64
- version: '0'
65
- type: :development
66
- prerelease: false
67
- version_requirements: !ruby/object:Gem::Requirement
68
- requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- version: '0'
72
- - !ruby/object:Gem::Dependency
73
- name: guard-rspec
74
- requirement: !ruby/object:Gem::Requirement
75
- requirements:
76
- - - ">="
77
- - !ruby/object:Gem::Version
78
- version: '0'
79
- type: :development
80
- prerelease: false
81
- version_requirements: !ruby/object:Gem::Requirement
82
- requirements:
83
- - - ">="
84
- - !ruby/object:Gem::Version
85
- version: '0'
86
- - !ruby/object:Gem::Dependency
87
- name: rb-fsevent
88
- requirement: !ruby/object:Gem::Requirement
89
- requirements:
90
- - - ">="
91
- - !ruby/object:Gem::Version
92
- version: '0'
93
- type: :development
94
- prerelease: false
95
- version_requirements: !ruby/object:Gem::Requirement
96
- requirements:
97
- - - ">="
98
- - !ruby/object:Gem::Version
99
- version: '0'
100
30
  description: Real time rate limiting for multi-process ruby environments based on
101
31
  Redis
102
32
  email:
@@ -109,7 +39,6 @@ extensions: []
109
39
  extra_rdoc_files: []
110
40
  files:
111
41
  - ".gitignore"
112
- - ".pairs"
113
42
  - ".rspec"
114
43
  - ".rvmrc"
115
44
  - ".travis.yml"
@@ -123,14 +52,18 @@ files:
123
52
  - lib/pause/analyzer.rb
124
53
  - lib/pause/configuration.rb
125
54
  - lib/pause/helper/timing.rb
55
+ - lib/pause/logger.rb
126
56
  - lib/pause/rate_limited_event.rb
127
57
  - lib/pause/redis/adapter.rb
58
+ - lib/pause/redis/sharded_adapter.rb
128
59
  - lib/pause/version.rb
129
60
  - pause.gemspec
130
61
  - spec/pause/action_spec.rb
131
62
  - spec/pause/analyzer_spec.rb
132
63
  - spec/pause/configuration_spec.rb
64
+ - spec/pause/pause_spec.rb
133
65
  - spec/pause/redis/adapter_spec.rb
66
+ - spec/pause/redis/sharded_adapter_spec.rb
134
67
  - spec/spec_helper.rb
135
68
  - spec/support/fakeredis.rb
136
69
  homepage: https://github.com/wanelo/pause
@@ -152,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
85
  version: '0'
153
86
  requirements: []
154
87
  rubyforge_project:
155
- rubygems_version: 2.2.0
88
+ rubygems_version: 2.2.3
156
89
  signing_key:
157
90
  specification_version: 4
158
91
  summary: RReal time rate limiting for multi-process ruby environments based on Redis
@@ -160,7 +93,8 @@ test_files:
160
93
  - spec/pause/action_spec.rb
161
94
  - spec/pause/analyzer_spec.rb
162
95
  - spec/pause/configuration_spec.rb
96
+ - spec/pause/pause_spec.rb
163
97
  - spec/pause/redis/adapter_spec.rb
98
+ - spec/pause/redis/sharded_adapter_spec.rb
164
99
  - spec/spec_helper.rb
165
100
  - spec/support/fakeredis.rb
166
- has_rdoc:
data/.pairs DELETED
@@ -1,13 +0,0 @@
1
- pairs:
2
- ag: Atasay Gokkaya; atasay
3
- km: Kaan Meralan; kaan
4
- kg: Konstantin Gredeskoul; kig
5
- ph: Paul Henry; paul
6
- sf: Sean Flannagan; sean
7
- es: Eric Saxby; sax
8
- tn: Truong Nguyen; constantx
9
- cc: Cihan Cimen; cihan
10
- sc: Server Cimen; server
11
- email:
12
- prefix: pair
13
- domain: wanelo.com