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 +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +30 -0
- data/MIT-LICENSE.txt +20 -0
- data/Rakefile +11 -0
- data/lib/redis_ring/client/operation_definitions.rb +147 -0
- data/lib/redis_ring/client/ring_meta_data.rb +72 -0
- data/lib/redis_ring/client/ring_proxy.rb +188 -0
- data/lib/redis_ring/client/shard_connection_pool.rb +33 -0
- data/lib/redis_ring/client/sharder.rb +30 -0
- data/lib/redis_ring/client/version.rb +5 -0
- data/lib/redis_ring/client.rb +10 -0
- data/redis_ring_client.gemspec +27 -0
- data/spec/fakes/fake_ring_meta_data.rb +15 -0
- data/spec/redis_ring/client/ring_meta_data_spec.rb +60 -0
- data/spec/redis_ring/client/ring_proxy_spec.rb +158 -0
- data/spec/redis_ring/client/shard_connection_pool_spec.rb +27 -0
- data/spec/redis_ring/client/sharder_spec.rb +40 -0
- data/spec/spec_helper.rb +10 -0
- metadata +134 -0
    
        data/Gemfile
    ADDED
    
    
    
        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,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,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
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    
    
        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 | 
            +
             |