redis_ring_client 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ .rvmrc
5
+ tags
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in redis_ring_client.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ redis_ring_client (0.0.1)
5
+ json
6
+ redis
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ diff-lcs (1.1.2)
12
+ json (1.5.1)
13
+ mocha (0.9.12)
14
+ redis (2.1.1)
15
+ rspec (2.5.0)
16
+ rspec-core (~> 2.5.0)
17
+ rspec-expectations (~> 2.5.0)
18
+ rspec-mocks (~> 2.5.0)
19
+ rspec-core (2.5.1)
20
+ rspec-expectations (2.5.0)
21
+ diff-lcs (~> 1.1.2)
22
+ rspec-mocks (2.5.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ mocha
29
+ redis_ring_client!
30
+ rspec
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Adam Pohorecki, http://adam.pohorecki.pl/
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+
7
+ desc "Run all specs"
8
+ RSpec::Core::RakeTask.new(:spec) do |t|
9
+ t.pattern = "./spec/**/*_spec.rb"
10
+ t.rspec_opts = ["--profile --color"]
11
+ end
@@ -0,0 +1,147 @@
1
+ module RedisRing
2
+ module Client
3
+
4
+ class UnsupportedOperationError < StandardError; end
5
+ class MultiShardOperationError < UnsupportedOperationError; end
6
+
7
+ module OperationDefinitions
8
+
9
+ def self.included(klass)
10
+ klass.send(:include, InstanceMethods)
11
+ klass.send(:include, GatherOperations)
12
+ klass.extend(ClassMethods)
13
+ end
14
+
15
+ module GatherOperations
16
+
17
+ def last_result(array)
18
+ return array.last
19
+ end
20
+
21
+ def sum(array)
22
+ return array.reduce(:+)
23
+ end
24
+
25
+ end
26
+
27
+ module InstanceMethods
28
+
29
+ def single_key_operation(name, first_arg, *rest)
30
+ connection = connection_for_key(first_arg)
31
+ return connection.send(name, first_arg, *rest)
32
+ end
33
+
34
+ def scather_gather_operation(name, gather, *args, &block)
35
+ results = []
36
+ each_connection do |conn|
37
+ results << conn.send(name, *args, &block)
38
+ end
39
+ return send(gather, results)
40
+ end
41
+
42
+ def unsupported_operation(name)
43
+ raise UnsupportedOperationError.new("Operation #{name} is not supported by RedisRing!")
44
+ end
45
+
46
+ def random_shard_operation(name, *args, &block)
47
+ shard_no = rand(ring_meta_data.ring_size)
48
+ return connection_pool.connection(shard_no).send(name, *args, &block)
49
+ end
50
+
51
+ def single_connection_operation(name, keys, *args, &block)
52
+ shard_numbers = keys.map { |key| sharder.shard_for_key(key) }
53
+ unless shard_numbers.uniq.size == 1
54
+ raise MultiShardOperationError.new("Multi-shard atomic operations are not allowed. Try using {shard_secifier} suffix if you really need them. Operation: #{name}, Keys: #{keys.join(', ')}")
55
+ end
56
+ return connection_for_key(keys.first).send(name, *args, &block)
57
+ end
58
+
59
+ end
60
+
61
+ module ClassMethods
62
+
63
+ def single_key_operation(name)
64
+ self.class_eval <<-RUBY
65
+
66
+ def #{name}(key, *args)
67
+ return single_key_operation(:#{name}, key, *args)
68
+ end
69
+
70
+ RUBY
71
+ end
72
+
73
+ def scather_gather_operation(name, gather_function)
74
+ self.class_eval <<-RUBY
75
+
76
+ def #{name}(*args, &block)
77
+ return scather_gather_operation(:#{name}, :#{gather_function}, *args, &block)
78
+ end
79
+
80
+ RUBY
81
+ end
82
+
83
+ def unsupported_operation(name)
84
+ self.class_eval <<-RUBY
85
+
86
+ def #{name}(*args)
87
+ unsupported_operation(:#{name})
88
+ end
89
+
90
+ RUBY
91
+ end
92
+
93
+ def random_shard_operation(name)
94
+ self.class_eval <<-RUBY
95
+
96
+ def #{name}(*args, &block)
97
+ random_shard_operation(:#{name}, *args, &block)
98
+ end
99
+
100
+ RUBY
101
+ end
102
+
103
+ def multi_key_operation(name)
104
+ self.class_eval <<-RUBY
105
+
106
+ def #{name}(*keys, &block)
107
+ return single_connection_operation(:#{name}, keys, *keys, &block)
108
+ end
109
+
110
+ RUBY
111
+ end
112
+
113
+ def mapped_set_operation(name)
114
+ self.class_eval <<-RUBY
115
+
116
+ def #{name}(hash, &block)
117
+ return single_connection_operation(:#{name}, hash.keys, hash, &block)
118
+ end
119
+
120
+ RUBY
121
+ end
122
+
123
+ def regular_set_operation(name)
124
+ self.class_eval <<-RUBY
125
+
126
+ def #{name}(*keys_and_values, &block)
127
+ return single_connection_operation(:#{name}, Hash[*keys_and_values].keys, *keys_and_values, &block)
128
+ end
129
+
130
+ RUBY
131
+ end
132
+
133
+ def multi_zstore_operation(name)
134
+ self.class_eval <<-RUBY
135
+
136
+ def #{name}(destination, keys, options = {})
137
+ return single_connection_operation(:#{name}, [destination] + keys, destination, keys, options)
138
+ end
139
+
140
+ RUBY
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,72 @@
1
+ module RedisRing
2
+ module Client
3
+
4
+ class UnknownShardError < StandardError; end
5
+
6
+ class RingMetaData
7
+
8
+ attr_reader :host, :port
9
+
10
+ def initialize(host, port)
11
+ @host = host
12
+ @port = port
13
+ @loaded = false
14
+ end
15
+
16
+ def reload!
17
+ json = get_shards_json_string
18
+ hash = JSON.parse(json)
19
+
20
+ @ring_size = hash['count']
21
+ @shards = (0...@ring_size).map{|n| ShardMetaData.from_json(hash['shards'][n.to_s])}
22
+
23
+ @loaded = true
24
+ end
25
+
26
+ def ring_size
27
+ reload! if should_reload?
28
+
29
+ return @ring_size
30
+ end
31
+
32
+ def shard(shard_number)
33
+ reload! if should_reload?
34
+
35
+ unless shard_number >= 0 && shard_number < ring_size
36
+ raise UnknownShardError.new("Shard number invalid: #{shard_number}. Ring size: #{ring_size}")
37
+ end
38
+
39
+ return @shards[shard_number]
40
+ end
41
+
42
+ protected
43
+
44
+ def should_reload?
45
+ !@loaded
46
+ end
47
+
48
+ def get_shards_json_string
49
+ Net::HTTP.get(host, '/shards', port)
50
+ end
51
+
52
+ end
53
+
54
+ class ShardMetaData
55
+
56
+ attr_reader :host, :port, :status
57
+
58
+ def initialize(host, port, status)
59
+ @host = host
60
+ @port = port
61
+ @status = status
62
+ end
63
+
64
+ def self.from_json(hash)
65
+ new(hash['host'], hash['port'].to_i, hash['status'].to_sym)
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+ end
72
+
@@ -0,0 +1,188 @@
1
+ module RedisRing
2
+ module Client
3
+
4
+ class RingProxy
5
+
6
+ include OperationDefinitions
7
+
8
+ single_key_operation :[]
9
+ single_key_operation :[]=
10
+ single_key_operation :append
11
+ single_key_operation :decr
12
+ single_key_operation :decrby
13
+ single_key_operation :exists
14
+ single_key_operation :expire
15
+ single_key_operation :expireat
16
+ single_key_operation :get
17
+ single_key_operation :getset
18
+ single_key_operation :hdel
19
+ single_key_operation :hexists
20
+ single_key_operation :hget
21
+ single_key_operation :hgetall
22
+ single_key_operation :hincrby
23
+ single_key_operation :hkeys
24
+ single_key_operation :hlen
25
+ single_key_operation :hmget
26
+ single_key_operation :hmset
27
+ single_key_operation :hset
28
+ single_key_operation :hsetnx
29
+ single_key_operation :hvals
30
+ single_key_operation :incr
31
+ single_key_operation :incrby
32
+ single_key_operation :lindex
33
+ single_key_operation :linsert
34
+ single_key_operation :llen
35
+ single_key_operation :lpop
36
+ single_key_operation :lpush
37
+ single_key_operation :lpushx
38
+ single_key_operation :lrange
39
+ single_key_operation :lrem
40
+ single_key_operation :lset
41
+ single_key_operation :ltrim
42
+ single_key_operation :mapped_hmget
43
+ single_key_operation :mapped_hmset
44
+ single_key_operation :move
45
+ single_key_operation :persist
46
+ single_key_operation :publish
47
+ single_key_operation :rpop
48
+ single_key_operation :rpush
49
+ single_key_operation :rpushx
50
+ single_key_operation :sadd
51
+ single_key_operation :scard
52
+ single_key_operation :set
53
+ single_key_operation :setex
54
+ single_key_operation :setnx
55
+ single_key_operation :sismember
56
+ single_key_operation :smembers
57
+ single_key_operation :sort
58
+ single_key_operation :spop
59
+ single_key_operation :srandmember
60
+ single_key_operation :srem
61
+ single_key_operation :strlen
62
+ single_key_operation :substr
63
+ single_key_operation :ttl
64
+ single_key_operation :type
65
+ single_key_operation :zadd
66
+ single_key_operation :zcard
67
+ single_key_operation :zcount
68
+ single_key_operation :zincrby
69
+ single_key_operation :zrange
70
+ single_key_operation :zrangebyscore
71
+ single_key_operation :zrank
72
+ single_key_operation :zrem
73
+ single_key_operation :zremrangebyrank
74
+ single_key_operation :zremrangebyscore
75
+ single_key_operation :zrevrange
76
+ single_key_operation :zrevrank
77
+ single_key_operation :zscore
78
+
79
+ scather_gather_operation :auth, :last_result
80
+ scather_gather_operation :bgrewriteaof, :last_result
81
+ scather_gather_operation :bgsave, :last_result
82
+ scather_gather_operation :config, :last_result
83
+ scather_gather_operation :dbsize, :sum
84
+ scather_gather_operation :flushall, :last_result
85
+ scather_gather_operation :flushdb, :last_result
86
+ scather_gather_operation :keys, :sum
87
+ scather_gather_operation :quit, :last_result
88
+ scather_gather_operation :save, :last_result
89
+ scather_gather_operation :select, :last_result
90
+ scather_gather_operation :shutdown, :last_result
91
+
92
+ # it might be useful to combine those, but it would break the interface
93
+ unsupported_operation :info
94
+
95
+ # could be used in a single server, but it's complicated
96
+ # maybe a TODO for the future
97
+ unsupported_operation :multi
98
+ unsupported_operation :watch
99
+ unsupported_operation :unwatch
100
+ unsupported_operation :discard
101
+ unsupported_operation :exec
102
+ unsupported_operation :pipelined
103
+
104
+ # there's no good way to scather_gather this
105
+ unsupported_operation :monitor
106
+
107
+ # maybe max or min from the shards?
108
+ unsupported_operation :lastsave
109
+
110
+ unsupported_operation :debug
111
+
112
+ # no way to determine which shards they fall into
113
+ unsupported_operation :psubscribe
114
+ unsupported_operation :punsubscribe
115
+ unsupported_operation :subscribed?
116
+
117
+ unsupported_operation :sync
118
+ unsupported_operation :slaveof
119
+
120
+ random_shard_operation :echo
121
+ random_shard_operation :ping
122
+ random_shard_operation :randomkey
123
+
124
+ multi_key_operation :blpop
125
+ multi_key_operation :brpop
126
+ multi_key_operation :del
127
+ multi_key_operation :mapped_mget
128
+ multi_key_operation :mget
129
+ multi_key_operation :rename
130
+ multi_key_operation :renamenx
131
+ multi_key_operation :rpoplpush
132
+ multi_key_operation :sdiff
133
+ multi_key_operation :sdiffstore
134
+ multi_key_operation :sinter
135
+ multi_key_operation :sinterstore
136
+ multi_key_operation :subscribe
137
+ multi_key_operation :sunion
138
+ multi_key_operation :sunionstore
139
+ multi_key_operation :unsubscribe
140
+
141
+ mapped_set_operation :mapped_mset
142
+ mapped_set_operation :mapped_msetnx
143
+ regular_set_operation :mset
144
+ regular_set_operation :msetnx
145
+
146
+ multi_zstore_operation :zinterstore
147
+ multi_zstore_operation :zunionstore
148
+
149
+ def smove(source, destination, member)
150
+ return single_connection_operation(:smove, [source, destination], source, destination, member)
151
+ end
152
+
153
+ def initialize(opts = {})
154
+ @host = opts[:host] || 'localhost'
155
+ @port = opts[:port] || 6400
156
+ @db = opts[:db] || 0
157
+ @password = opts[:password]
158
+ end
159
+
160
+ def connection_for_key(key)
161
+ shard = sharder.shard_for_key(key)
162
+ return connection_pool.connection(shard)
163
+ end
164
+
165
+ def each_connection(&block)
166
+ ring_meta_data.ring_size.times do |shard_no|
167
+ block.call(connection_pool.connection(shard_no))
168
+ end
169
+ end
170
+
171
+ protected
172
+
173
+ def ring_meta_data
174
+ @ring_meta_data ||= RingMetaData.new(@host, @port)
175
+ end
176
+
177
+ def sharder
178
+ @sharder ||= Sharder.new(ring_meta_data)
179
+ end
180
+
181
+ def connection_pool
182
+ @connection_pool = ShardConnectionPool.new(ring_meta_data, @password, @db)
183
+ end
184
+
185
+ end
186
+
187
+ end
188
+ end
@@ -0,0 +1,33 @@
1
+ module RedisRing
2
+ module Client
3
+
4
+ class ShardConnectionPool
5
+
6
+ attr_reader :metadata, :password, :db
7
+
8
+ def initialize(metadata, password, db)
9
+ @metadata = metadata
10
+ @password = password
11
+ @db = db
12
+ @connections = {}
13
+ end
14
+
15
+ def connection(shard_number)
16
+ @connections[shard_number] ||= new_connection_to_shard(shard_number)
17
+ end
18
+
19
+ protected
20
+
21
+ def new_connection_to_shard(shard_number)
22
+ shard_metadata = metadata.shard(shard_number)
23
+ new_connection(shard_metadata.host, shard_metadata.port, db, password)
24
+ end
25
+
26
+ def new_connection(host, port, db, password)
27
+ Redis.new(:host => host, :port => port, :db => db, :password => password)
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ module RedisRing
2
+ module Client
3
+
4
+ class Sharder
5
+
6
+ attr_reader :metadata
7
+
8
+ def initialize(metadata)
9
+ @metadata = metadata
10
+ end
11
+
12
+ def shard_for_key(key)
13
+ crc = Zlib.crc32(hashable_part(key.to_s))
14
+ return crc % metadata.ring_size
15
+ end
16
+
17
+ private
18
+
19
+ def hashable_part(key)
20
+ if key =~ /{([^}]*)}$/
21
+ return Regexp.last_match(1)
22
+ else
23
+ return key
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ module RedisRing
2
+ module Client
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require 'zlib'
2
+
3
+ require 'redis'
4
+ require 'json'
5
+
6
+ require 'redis_ring/client/operation_definitions'
7
+ require 'redis_ring/client/ring_proxy'
8
+ require 'redis_ring/client/sharder'
9
+ require 'redis_ring/client/ring_meta_data'
10
+ require 'redis_ring/client/shard_connection_pool'
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "redis_ring/client/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "redis_ring_client"
7
+ s.version = RedisRing::Client::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Adam Pohorecki"]
10
+ s.email = ["adam@pohorecki.pl"]
11
+ s.homepage = "http://github.com/psyho/redis_ring_client"
12
+ s.summary = %q{Client for RedisRing}
13
+ s.description = %q{The client counterpart to the RedisRing gem.}
14
+
15
+ s.rubyforge_project = "redis_ring_client"
16
+
17
+ s.add_dependency 'redis'
18
+ s.add_dependency 'json'
19
+
20
+ s.add_development_dependency 'rspec'
21
+ s.add_development_dependency 'mocha'
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,15 @@
1
+ class FakeRingMetaData
2
+
3
+ attr_accessor :ring_size
4
+ attr_accessor :shards
5
+
6
+ def initialize(ring_size)
7
+ @ring_size = ring_size
8
+ @shards = {}
9
+ end
10
+
11
+ def shard(shard_no)
12
+ return @shards[shard_no]
13
+ end
14
+
15
+ end
@@ -0,0 +1,60 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe RedisRing::Client::RingMetaData do
4
+
5
+ def sample_shards_hash
6
+ {
7
+ :count => 10,
8
+ :shards => {
9
+ 0 => {:host => '192.168.1.1', :port => 6401, :status => :running},
10
+ 1 => {:host => '192.168.1.1', :port => 6402, :status => :running},
11
+ 2 => {:host => '192.168.1.1', :port => 6403, :status => :running},
12
+ 3 => {:host => '192.168.1.1', :port => 6404, :status => :running},
13
+ 4 => {:host => '192.168.1.1', :port => 6405, :status => :running},
14
+ 5 => {:host => '192.168.1.1', :port => 6406, :status => :running},
15
+ 6 => {:host => '192.168.1.1', :port => 6407, :status => :running},
16
+ 7 => {:host => '192.168.1.1', :port => 6408, :status => :running},
17
+ 8 => {:host => '192.168.1.1', :port => 6409, :status => :running},
18
+ 9 => {:host => '192.168.1.1', :port => 6410, :status => :running}
19
+ }
20
+ }
21
+ end
22
+
23
+ def sample_shard_json
24
+ sample_shards_hash.to_json
25
+ end
26
+
27
+ it "should download json lazily" do
28
+ @metadata = RedisRing::Client::RingMetaData.new('host', 666)
29
+
30
+ Net::HTTP.expects(:get).with('host', '/shards', 666).returns(sample_shard_json)
31
+
32
+ @metadata.ring_size.should == 10
33
+ end
34
+
35
+ context "with sample shards json" do
36
+ before(:each) do
37
+ Net::HTTP.stubs(:get => sample_shard_json)
38
+
39
+ @metadata = RedisRing::Client::RingMetaData.new('host', 666)
40
+ end
41
+
42
+ it "should have ring_size of 10" do
43
+ @metadata.ring_size.should == 10
44
+ end
45
+
46
+ it "should have 10 shards" do
47
+ 10.times do |n|
48
+ @metadata.shard(n).host.should == '192.168.1.1'
49
+ @metadata.shard(n).port.should == 6401 + n
50
+ @metadata.shard(n).status.should == :running
51
+ end
52
+ end
53
+
54
+ it "should raise an exception when trying to acces unexisting shard metadata" do
55
+ lambda {
56
+ @metadata.shard(10)
57
+ }.should raise_exception(RedisRing::Client::UnknownShardError)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,158 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe RedisRing::Client::RingProxy do
4
+
5
+ def readable_params(params)
6
+ translated = params.map do |type, name|
7
+ case type
8
+ when :req then name.to_s
9
+ when :rest then "*#{name}"
10
+ when :block then "&#{name}"
11
+ when :opt then "#{name} = some_value"
12
+ else
13
+ raise "Unknown parameter type #{type}"
14
+ end
15
+ end
16
+
17
+ return translated.join(", ")
18
+ end
19
+
20
+ it "should have the same public interface as Redis" do
21
+ difference = Redis.public_instance_methods - RedisRing::Client::RingProxy.public_instance_methods
22
+
23
+ ignored = [:client, :id, :method_missing]
24
+
25
+ difference -= ignored
26
+
27
+ unless difference == []
28
+ puts "#{difference.size} missing methods:"
29
+
30
+ difference.sort.each do |method_name|
31
+ puts "#{method_name}(#{readable_params(Redis.instance_method(method_name).parameters)})"
32
+ end
33
+
34
+ fail("Not all methods implemented")
35
+ end
36
+ end
37
+
38
+ context "with real RedisRing" do
39
+ before(:each) do
40
+ @proxy = RedisRing::Client::RingProxy.new
41
+ @proxy.flushdb
42
+ end
43
+
44
+ describe "single key operations" do
45
+ it "should have simple key operations implemented" do
46
+ @proxy.set('foo', 1)
47
+
48
+ @proxy.get('foo').should == '1'
49
+ end
50
+
51
+ it "should have string operations implemented" do
52
+ 3.times { @proxy.append('foo', 'bar') }
53
+ @proxy.incr('bar')
54
+ @proxy.incrby('bar', 10)
55
+ @proxy.decrby('bar', 3)
56
+
57
+ @proxy.strlen('foo').should == 9
58
+ @proxy.get('bar').should == '8'
59
+ end
60
+
61
+ it "should have list operations implemented" do
62
+ 3.times { |n| @proxy.lpush('foo', "bar#{n}") }
63
+
64
+ @proxy.llen('foo').should == 3
65
+ @proxy.lpop('foo').should == 'bar2'
66
+ @proxy.rpop('foo').should == 'bar0'
67
+ end
68
+
69
+ it "should have set operations implemented" do
70
+ 3.times { |n| @proxy.sadd('foo', "bar#{n}") }
71
+
72
+ @proxy.scard('foo').should == 3
73
+ @proxy.smembers('foo').sort.should == ['bar0', 'bar1', 'bar2']
74
+ end
75
+
76
+ it "should have zset operations implemented" do
77
+ @proxy.zadd 'foo', 1, 'bar'
78
+ @proxy.zadd 'foo', 3, 'baz'
79
+ @proxy.zadd 'foo', 2, 'bam'
80
+
81
+ @proxy.zcard('foo').should == 3
82
+ @proxy.zcount('foo', 1, 2).should == 2
83
+ @proxy.zrange('foo', 0, -1).should == ['bar', 'bam', 'baz']
84
+ @proxy.zrange('foo', 0, -1, :with_scores => true).should == ['bar', '1', 'bam', '2', 'baz', '3']
85
+ end
86
+
87
+ it "should have hash operations implemented" do
88
+ @proxy.hset 'foo', 'bar', 'hello'
89
+ 3.times { @proxy.hincrby 'foo', 'baz', 2 }
90
+
91
+ @proxy.hkeys('foo').should == ['bar', 'baz']
92
+ @proxy.hgetall('foo').should == {'bar' => 'hello', 'baz' => '6'}
93
+ end
94
+ end
95
+
96
+ describe "multi key operations" do
97
+ it "should allow multi key operations as long as they operate on the same shard" do
98
+ @proxy.mset 'foo{one}', 1, 'bar{one}', 2
99
+
100
+ @proxy.mget('foo{one}', 'bar{one}').should == ['1', '2']
101
+ end
102
+
103
+ it "should raise an exception if running an operation on multiple shards" do
104
+ lambda{
105
+ @proxy.mapped_mset 'foo{one}' => 1, 'bar{two}' => 2
106
+ }.should raise_exception(RedisRing::Client::MultiShardOperationError)
107
+ end
108
+
109
+ it "should work with multi key zset operations" do
110
+ @proxy.zadd 'foo{one}', 1, 1
111
+ @proxy.zadd 'foo{one}', 1, 2
112
+ @proxy.zadd 'foo{one}', 1, 3
113
+
114
+ @proxy.zadd 'bar{one}', 1, 2
115
+ @proxy.zadd 'bar{one}', 1, 3
116
+ @proxy.zadd 'bar{one}', 1, 4
117
+
118
+ @proxy.zinterstore 'baz{one}', ['foo{one}', 'bar{one}']
119
+
120
+ @proxy.zrange('baz{one}', 0, -1).should == ['2', '3']
121
+ end
122
+ end
123
+
124
+ describe "scather-gather operations" do
125
+ it "should sum dbsize and similar operations" do
126
+ keys = %w{ala ma kota kot ali nazywa sie as}
127
+ keys.each_with_index { |key, idx| @proxy.set(key, idx) }
128
+
129
+ @proxy.keys('*').sort.should == keys.sort
130
+ @proxy.dbsize.should == keys.size
131
+ end
132
+
133
+ it "should return last_result for operations where result is not important" do
134
+ @proxy.bgsave.should == "Background saving started"
135
+ @proxy.flushdb.should == "OK"
136
+ end
137
+ end
138
+
139
+ describe "unsupported operations" do
140
+ it "should raise an exception when using an operation with undefined behavior in RedisRing" do
141
+ lambda {
142
+ @proxy.multi do
143
+ @proxy.set 'foo{one}', 1
144
+ @proxy.set 'foo{two}', 2
145
+ end
146
+ }.should raise_exception(RedisRing::Client::UnsupportedOperationError)
147
+ end
148
+ end
149
+
150
+ describe "random shard operations" do
151
+ it "should execute the operation on a random shard" do
152
+ @proxy.ping.should == "PONG"
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe RedisRing::Client::ShardConnectionPool do
4
+
5
+ before(:each) do
6
+ @metadata = FakeRingMetaData.new(5)
7
+ 5.times do |n|
8
+ @metadata.shards[n] = RedisRing::Client::ShardMetaData.new("host#{n}", 666 + n, :running)
9
+ end
10
+
11
+ @connection_pool = RedisRing::Client::ShardConnectionPool.new(@metadata, @password = nil, @db = 10)
12
+ end
13
+
14
+ it "should create a new connection when there was no shard connection before" do
15
+ @connection_pool.expects(:new_connection).with("host1", 667, @db, @password).returns(:foo).once
16
+
17
+ @connection_pool.connection(1).should == :foo
18
+ end
19
+
20
+ it "should cache connections" do
21
+ @connection_pool.expects(:new_connection).with("host1", 667, @db, @password).returns(:foo).once
22
+
23
+ @connection_pool.connection(1).should == :foo
24
+ @connection_pool.connection(1).should == :foo
25
+ end
26
+
27
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../../../spec_helper', __FILE__)
2
+
3
+ describe RedisRing::Client::Sharder do
4
+
5
+ before(:each) do
6
+ @sharder = RedisRing::Client::Sharder.new(@meta_data = FakeRingMetaData.new(1024))
7
+ end
8
+
9
+ it "should hash the same value always to the same shard" do
10
+ shards = (0..9).map{|n| @sharder.shard_for_key("some_key")}
11
+
12
+ shards.uniq.size.should == 1
13
+ end
14
+
15
+ it "should never return less than 0 or more than ring_size - 1" do
16
+ ['foo', 'bar', 'baz'].product(['0', '1', '2']).product(['a', 'b', 'c']).each do |arr|
17
+ str = arr.flatten.join('_')
18
+ shard = @sharder.shard_for_key(str)
19
+
20
+ shard.should >= 0
21
+ shard.should < 1024
22
+ end
23
+ end
24
+
25
+ it "should be sensitive to ring_size change" do
26
+ old_val = @sharder.shard_for_key('foo')
27
+ @meta_data.ring_size = 100
28
+ @sharder.shard_for_key('foo').should_not == old_val
29
+ end
30
+
31
+ it "should return different shards for slightly different values" do
32
+ @sharder.shard_for_key('foo1').should_not == @sharder.shard_for_key('foo2')
33
+ end
34
+
35
+ it "should take advantage of the {shard} specifier" do
36
+ @sharder.shard_for_key('foo1{one}').should == @sharder.shard_for_key('foo2{one}')
37
+ @sharder.shard_for_key('foo1{one}').should == @sharder.shard_for_key('one')
38
+ end
39
+
40
+ end
@@ -0,0 +1,10 @@
1
+ $:.push File.expand_path("../../lib", __FILE__)
2
+
3
+ require 'redis_ring/client'
4
+
5
+ require File.expand_path('../fakes/fake_ring_meta_data', __FILE__)
6
+
7
+ RSpec.configure do |c|
8
+ c.color_enabled = true
9
+ c.mock_with :mocha
10
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis_ring_client
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Adam Pohorecki
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-08 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: redis
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: json
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :development
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: mocha
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ type: :development
71
+ version_requirements: *id004
72
+ description: The client counterpart to the RedisRing gem.
73
+ email:
74
+ - adam@pohorecki.pl
75
+ executables: []
76
+
77
+ extensions: []
78
+
79
+ extra_rdoc_files: []
80
+
81
+ files:
82
+ - .gitignore
83
+ - Gemfile
84
+ - Gemfile.lock
85
+ - MIT-LICENSE.txt
86
+ - Rakefile
87
+ - lib/redis_ring/client.rb
88
+ - lib/redis_ring/client/operation_definitions.rb
89
+ - lib/redis_ring/client/ring_meta_data.rb
90
+ - lib/redis_ring/client/ring_proxy.rb
91
+ - lib/redis_ring/client/shard_connection_pool.rb
92
+ - lib/redis_ring/client/sharder.rb
93
+ - lib/redis_ring/client/version.rb
94
+ - redis_ring_client.gemspec
95
+ - spec/fakes/fake_ring_meta_data.rb
96
+ - spec/redis_ring/client/ring_meta_data_spec.rb
97
+ - spec/redis_ring/client/ring_proxy_spec.rb
98
+ - spec/redis_ring/client/shard_connection_pool_spec.rb
99
+ - spec/redis_ring/client/sharder_spec.rb
100
+ - spec/spec_helper.rb
101
+ has_rdoc: true
102
+ homepage: http://github.com/psyho/redis_ring_client
103
+ licenses: []
104
+
105
+ post_install_message:
106
+ rdoc_options: []
107
+
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ segments:
116
+ - 0
117
+ version: "0"
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ segments:
124
+ - 0
125
+ version: "0"
126
+ requirements: []
127
+
128
+ rubyforge_project: redis_ring_client
129
+ rubygems_version: 1.3.7
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: Client for RedisRing
133
+ test_files: []
134
+