mitchellh-lightcloud 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CREDITS ADDED
@@ -0,0 +1,9 @@
1
+ ======================================
2
+ = lightcloud credits =
3
+ ======================================
4
+
5
+ Original Author (Python Implementation):
6
+ Amir Salihefendic http://amix.dk
7
+
8
+ Ported to Ruby by:
9
+ Mitchell Hashimoto http://mitchellhashimoto.com
data/LICENSE ADDED
@@ -0,0 +1,10 @@
1
+ Copyright (c) 2009, Mitchell Hashimoto
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ * Neither the name of the owner nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9
+
10
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.rdoc ADDED
@@ -0,0 +1,34 @@
1
+ = LightCloud Library for Ruby
2
+
3
+ This is a library for accessing LightCloud systems through Ruby.
4
+
5
+ == Background
6
+
7
+ LightCloud is a distributed key-value stored open-sourced by Plurk.
8
+ The official website which includes benchmarks, design specs, and
9
+ more can be viewed at the following URL:
10
+
11
+ http://opensource.plurk.com/LightCloud/
12
+
13
+ == Usage
14
+
15
+ require 'rubygems'
16
+ require 'lightcloud'
17
+
18
+ LIGHT_CLOUD = {
19
+ 'lookup1_A' => ['127.0.0.1:41401', '127.0.0.1:41402'],
20
+ 'storage1_A' => ['192.168.0.2:51401', '192.168.0.2:51402']
21
+ }
22
+
23
+ lookup_nodes, storage_nodes = LightCloud.generate_nodes(LIGHT_CLOUD)
24
+ LightCloud.init(lookup_nodes, storage_nodes)
25
+
26
+ LightCloud.set("hello", "world")
27
+ print LightCloud.get("hello") # => world
28
+ LightCloud.delete("hello")
29
+
30
+ print LightCloud.get("hello") # => nil
31
+
32
+ == Installation
33
+
34
+ sudo gem install mitchellh-lightcloud
data/Rakefile ADDED
@@ -0,0 +1,90 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/packagetask'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'spec/rake/spectask'
7
+ require 'fileutils'
8
+
9
+ ###################################
10
+ # Clean & Defaut Task
11
+ ###################################
12
+ CLEAN.include('dist','tmp','rdoc')
13
+ task :default => [:clean, :repackage]
14
+
15
+ ###################################
16
+ # Specs
17
+ ###################################
18
+ desc "Run all specs for hash_ring"
19
+ Spec::Rake::SpecTask.new('spec') do |t|
20
+ t.spec_files = FileList['spec/**/*.rb']
21
+ end
22
+
23
+ ###################################
24
+ # Docs
25
+ ###################################
26
+ Rake::RDocTask.new do |rd|
27
+ rd.main = 'README.rdoc'
28
+
29
+ rd.rdoc_dir = 'doc'
30
+
31
+ rd.rdoc_files.include(
32
+ 'README.rdoc',
33
+ 'LICENSE',
34
+ 'CREDITS',
35
+ 'lib/**/*.rb')
36
+
37
+ rd.title = 'lightcloud'
38
+
39
+ rd.options << '-N' # line numbers
40
+ rd.options << '-S' # inline source
41
+ end
42
+
43
+ ###################################
44
+ # Packaging - Thank you Sinatra
45
+ ###################################
46
+ # Load the gemspec using the same limitations as github
47
+ def spec
48
+ @spec ||=
49
+ begin
50
+ require 'rubygems/specification'
51
+ data = File.read('lightcloud.gemspec')
52
+ spec = nil
53
+ Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
54
+ spec
55
+ end
56
+ end
57
+
58
+ def package(ext='')
59
+ "dist/lightcloud-#{spec.version}" + ext
60
+ end
61
+
62
+ desc 'Build packages'
63
+ task :package => %w[.gem .tar.gz].map {|e| package(e)}
64
+
65
+ desc 'Build and install as local gem'
66
+ task :install => package('.gem') do
67
+ sh "gem install #{package('.gem')}"
68
+ end
69
+
70
+ directory 'dist/'
71
+ CLOBBER.include('dist')
72
+
73
+ file package('.gem') => %w[dist/ lightcloud.gemspec] + spec.files do |f|
74
+ sh "gem build hash_ring.gemspec"
75
+ mv File.basename(f.name), f.name
76
+ end
77
+
78
+ file package('.tar.gz') => %w[dist/] + spec.files do |f|
79
+ sh <<-SH
80
+ git archive \
81
+ --prefix=lightcloud-#{source_version}/ \
82
+ --format=tar \
83
+ HEAD | gzip > #{f.name}
84
+ SH
85
+ end
86
+
87
+ def source_version
88
+ line = File.read('lib/lightcloud.rb')[/^\s*VERSION = .*/]
89
+ line.match(/.*VERSION = '(.*)'/)[1]
90
+ end
data/lib/lightcloud.rb ADDED
@@ -0,0 +1,215 @@
1
+ ######################################
2
+ # LightCloud
3
+ # Code ported from Python version written by Amir Salihefendic
4
+ ######################################
5
+ # Copyright (c) 2009, Mitchell Hashimoto, mitchell.hashimoto@gmail.com
6
+ #
7
+
8
+ require 'rubygems'
9
+ require 'hash_ring'
10
+ require 'rufus/tokyo'
11
+
12
+ require File.join(File.dirname(__FILE__), 'tyrant_client')
13
+
14
+ # = LightCloud Library
15
+ #
16
+ # == Background
17
+ #
18
+ # == Usage
19
+ #
20
+ # require 'rubygems'
21
+ # require 'lightcloud'
22
+ #
23
+ # LIGHT_CLOUD = {
24
+ # 'lookup1_A' => ['127.0.0.1:41401', '127.0.0.1:41402'],
25
+ # 'storage1_A' => ['192.168.0.2:51401', '192.168.0.2:51402']
26
+ # }
27
+ #
28
+ # lookup_nodes, storage_nodes = LightCloud.generate_nodes(LIGHT_CLOUD)
29
+ # LightCloud.init(lookup_nodes, storage_nodes)
30
+ #
31
+ # LightCloud.set("hello", "world")
32
+ # print LightCloud.get("hello") # => world
33
+ # LightCloud.delete("hello")
34
+ #
35
+ # print LightCloud.get("hello") # => nil
36
+ #
37
+ class LightCloud
38
+ VERSION = '0.1'
39
+ DEFAULT_SYSTEM = 'default'
40
+
41
+ @@systems = {}
42
+
43
+ def self.init(lookup_nodes, storage_nodes, system=DEFAULT_SYSTEM)
44
+ lookup_ring, name_to_l_nodes = self.generate_ring(lookup_nodes)
45
+ storage_ring, name_to_s_nodes = self.generate_ring(storage_nodes)
46
+
47
+ @@systems[system] = [lookup_ring, storage_ring, name_to_l_nodes, name_to_s_nodes]
48
+ end
49
+
50
+ #--
51
+ # Get/Set/Delete
52
+ #++
53
+ #
54
+ # Sets a value to a key in the LightCloud system.
55
+ #
56
+ # Set first checks to see if the key is already stored. If it is
57
+ # it uses that same node to store the new value. Otherwise, it
58
+ # determines where to store the value based on the hash_ring
59
+ def self.set(key, value, system=DEFAULT_SYSTEM)
60
+ storage_node = self.locate_node_or_init(key, system)
61
+ return storage_node.set(key, value)
62
+ end
63
+
64
+ #
65
+ # Gets a value based on a key.
66
+ def self.get(key, system=DEFAULT_SYSTEM)
67
+ result = nil
68
+
69
+ # Try to lookup key directly
70
+ storage_node = self.get_storage_ring(system).get_node(key)
71
+ value = storage_node.get(key)
72
+
73
+ result = value unless value.nil?
74
+
75
+ # Else use the lookup ring
76
+ if result.nil?
77
+ storage_node = self.locate_node(key, system)
78
+
79
+ result = storage_node.get(key) unless storage_node.nil?
80
+ end
81
+
82
+ result
83
+ end
84
+
85
+ #
86
+ # Lookup the key and delete it from both the storage ring
87
+ # and lookup ring
88
+ def self.delete(key, system=DEFAULT_SYSTEM)
89
+ storage_node = self.locate_node(key, system)
90
+
91
+ storage_node = get_storage_ring(system).get_node(key) if storage_node.nil?
92
+ lookup_nodes = get_lookup_ring(system).iterate_nodes(key)
93
+ lookup_nodes.each_index do |i|
94
+ break if i > 1
95
+
96
+ lookup_nodes[i].delete(key)
97
+ end
98
+
99
+ storage_node.delete(key) unless storage_node.nil?
100
+ true
101
+ end
102
+
103
+ #--
104
+ # Lookup Cloud
105
+ #++
106
+ def self.locate_node_or_init(key, system)
107
+ storage_node = self.locate_node(key, system)
108
+
109
+ if storage_node.nil?
110
+ storage_node = self.get_storage_ring(system).get_node(key)
111
+
112
+ lookup_node = self.get_lookup_ring(system).get_node(key)
113
+ lookup_node.set(key, storage_node.to_s)
114
+ end
115
+
116
+ storage_node
117
+ end
118
+
119
+ #
120
+ # Locates a node in the lookup ring, returning the node if it is found, or
121
+ # nil otherwise.
122
+ def self.locate_node(key, system=DEFAULT_SYSTEM)
123
+ nodes = self.get_lookup_ring(system).iterate_nodes(key)
124
+
125
+ lookups = 0
126
+ value = nil
127
+ nodes.each_index do |i|
128
+ lookups = i
129
+ return nil if lookups > 2
130
+
131
+ node = nodes[lookups]
132
+ value = node.get(key)
133
+
134
+ break unless value.nil?
135
+ end
136
+
137
+ return nil if value.nil?
138
+
139
+ if lookups == 0
140
+ return self.get_storage_node(value, system)
141
+ else
142
+ return self._clean_up_ring(key, value, system)
143
+ end
144
+ end
145
+
146
+ def self._clean_up_ring(key, value, system)
147
+ nodes = self.get_lookup_ring(system).iterate_nodes(key)
148
+
149
+ nodes.each_index do |i|
150
+ break if i > 1
151
+
152
+ node = nodes[i]
153
+ if i == 0
154
+ node.set(key, value)
155
+ else
156
+ node.delete(key)
157
+ end
158
+ end
159
+
160
+ return self.get_storage_node(value, system)
161
+ end
162
+
163
+ #--
164
+ # Accessors for rings
165
+ #++
166
+ def self.get_lookup_ring(system=DEFAULT_SYSTEM)
167
+ @@systems[system][0]
168
+ end
169
+
170
+ def self.get_storage_ring(system=DEFAULT_SYSTEM)
171
+ @@systems[system][1]
172
+ end
173
+
174
+ #--
175
+ # Accessors for nodes
176
+ #++
177
+ def self.get_storage_node(name, system=DEFAULT_SYSTEM)
178
+ @@systems[system][3][name]
179
+ end
180
+
181
+ #--
182
+ # Helpers
183
+ #++
184
+ def self.generate_nodes(config)
185
+ lookup_nodes = {}
186
+ storage_nodes = {}
187
+
188
+ config.each do |k,v|
189
+ if k.include?("lookup")
190
+ lookup_nodes[k] = v
191
+ elsif k.include?("storage")
192
+ storage_nodes[k] = v
193
+ end
194
+ end
195
+
196
+ return lookup_nodes, storage_nodes
197
+ end
198
+
199
+ #
200
+ # Given a set of nodes it creates the the nodes as Tokyo
201
+ # Tyrant objects and returns a hash ring with them
202
+ def self.generate_ring(nodes)
203
+ objects = []
204
+ name_to_obj = {}
205
+
206
+ nodes.each do |name, nodelist|
207
+ obj = TyrantNode.new(name, nodelist)
208
+ name_to_obj[name] = obj
209
+
210
+ objects.push(obj)
211
+ end
212
+
213
+ return HashRing.new(objects), name_to_obj
214
+ end
215
+ end
@@ -0,0 +1,239 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_base')
2
+
3
+ describe LightCloud do
4
+ before do
5
+ @valid_servers = {
6
+ 'lookup1_A' => ['127.0.0.1:1234', '127.0.0.1:4567'],
7
+ 'storage1_A' => ['127.0.0.2:1234', '127.0.0.2:4567']
8
+ }
9
+
10
+ @valid_lookup_nodes, @valid_storage_nodes = LightCloud.generate_nodes(@valid_servers)
11
+
12
+ @generic_node = mock(TyrantNode)
13
+ [:get, :set, :delete].each do |meth|
14
+ @generic_node.stub!(meth).and_return(nil)
15
+ end
16
+
17
+ @nodes = [mock(TyrantNode), mock(TyrantNode), mock(TyrantNode)]
18
+ @lookup_valid_node = mock(TyrantNode)
19
+ @storage_valid_node = mock(TyrantNode)
20
+
21
+ (@nodes + [@lookup_valid_node, @storage_valid_node]).each do |node|
22
+ node.stub!(:get).and_return(nil)
23
+ node.stub!(:set).and_return(nil)
24
+ node.stub!(:delete).and_return(nil)
25
+ node.stub!(:to_s).and_return(nil)
26
+ end
27
+
28
+ @storage_valid_node.stub!(:to_s).and_return('storage_valid_node')
29
+
30
+ @lookup_ring = mock(HashRing)
31
+ @lookup_ring.stub!(:iterate_nodes).and_return(@nodes)
32
+ @lookup_ring.stub!(:get_node).and_return(@lookup_valid_node)
33
+
34
+ @storage_ring = mock(HashRing)
35
+ @storage_ring.stub!(:get_node).and_return(@storage_valid_node)
36
+
37
+ LightCloud.stub!(:get_lookup_ring).and_return(@lookup_ring)
38
+ LightCloud.stub!(:get_storage_ring).and_return(@storage_ring)
39
+ end
40
+
41
+ describe "node generation" do
42
+ it "should split lookup and storage nodes into their own arrays" do
43
+ lookup, storage = LightCloud.generate_nodes(@valid_servers)
44
+
45
+ lookup.should be_has_key('lookup1_A')
46
+ storage.should be_has_key('storage1_A')
47
+ end
48
+
49
+ it "should ignore configuration without 'lookup' or 'storage' in it" do
50
+ @valid_servers['foobarbaz'] = []
51
+
52
+ lookup, storage = LightCloud.generate_nodes(@valid_servers)
53
+
54
+ lookup.should_not be_has_key('foobarbaz')
55
+ storage.should_not be_has_key('foobarbaz')
56
+ end
57
+ end
58
+
59
+ describe "ring generation" do
60
+ it "should create a TyrantNode for each node" do
61
+ TyrantNode.should_receive(:new).with('lookup1_A', anything).once
62
+ TyrantNode.should_receive(:new).with('storage1_A', anything).once
63
+
64
+ LightCloud.init(@valid_lookup_nodes, @valid_storage_nodes)
65
+ end
66
+
67
+ it "should return the tyrant nodes as a name to node hash" do
68
+ unneeded, name_to_nodes = LightCloud.generate_ring(@valid_lookup_nodes)
69
+
70
+ name_to_nodes.should be_has_key('lookup1_A')
71
+ name_to_nodes['lookup1_A'].should be_kind_of(TyrantNode)
72
+ end
73
+
74
+ it "should return a hash ring with the nodes" do
75
+ ring, unneeded = LightCloud.generate_ring(@valid_lookup_nodes)
76
+
77
+ ring.should be_kind_of(HashRing)
78
+ end
79
+ end
80
+
81
+ describe "lookup cloud methods" do
82
+ before do
83
+ @key = 'foo'
84
+ @storage_node = 'bar'
85
+ end
86
+
87
+ describe "locating or initting a storage node by key" do
88
+ after do
89
+ LightCloud.should_receive(:locate_node).with(@key, anything).once.and_return(@storage_node)
90
+ LightCloud.locate_node_or_init(@key, LightCloud::DEFAULT_SYSTEM)
91
+ end
92
+
93
+ it "should just return the storage node if it was found" do
94
+ LightCloud.should_not_receive(:get_storage_ring)
95
+ LightCloud.should_not_receive(:get_lookup_ring)
96
+ end
97
+
98
+ it "should set the lookup ring to point to the new storage node if no previous storage node was found" do
99
+ @storage_node = nil
100
+
101
+ @storage_ring.should_receive(:get_node).with(@key).once.and_return(@storage_valid_node)
102
+ @lookup_valid_node.should_receive(:set).with(@key, @storage_valid_node.to_s).once
103
+ end
104
+ end
105
+
106
+ describe "locating a storage node by key" do
107
+ it "should return the storage node if the key is found in the lookup ring" do
108
+ @nodes[0].should_receive(:get).with(@key).and_return(@storage_node)
109
+ LightCloud.should_receive(:get_storage_node).with(@storage_node, anything).once
110
+
111
+ LightCloud.locate_node(@key)
112
+ end
113
+
114
+ it "should return nil if the key doesn't exist in the lookup ring" do
115
+ LightCloud.locate_node(@key).should be_nil
116
+ end
117
+
118
+ it "should attempt to clean up the lookup ring if the value is NOT found in the first node" do
119
+ @nodes[1].should_receive(:get).with(@key).and_return(@storage_node)
120
+ LightCloud.should_not_receive(:get_storage_node)
121
+ LightCloud.should_receive(:_clean_up_ring).with(@key, @storage_node, anything).once
122
+
123
+ LightCloud.locate_node(@key)
124
+ end
125
+ end
126
+
127
+ describe "cleaning the lookup ring" do
128
+ after do
129
+ LightCloud._clean_up_ring(@key, @storage_node, LightCloud::DEFAULT_SYSTEM)
130
+ end
131
+
132
+ it "should set the key/value onto the first node (index 0)" do
133
+ @nodes[0].should_receive(:set).with(@key, @storage_node).once
134
+ end
135
+
136
+ it "should delete the key from the second node (index 1)" do
137
+ @nodes[1].should_receive(:delete).with(@key).once
138
+ end
139
+
140
+ it "should not touch any other nodes" do
141
+ @nodes[2].should_not_receive(:get)
142
+ @nodes[2].should_not_receive(:set)
143
+ @nodes[2].should_not_receive(:delete)
144
+ end
145
+
146
+ it "should return the storage node lookup" do
147
+ LightCloud.should_receive(:get_storage_node).with(@storage_node, anything).once
148
+ end
149
+ end
150
+ end
151
+
152
+ describe "setting" do
153
+ before do
154
+ @key = 'hello'
155
+ @value = 'world!'
156
+
157
+ LightCloud.stub!(:locate_node_or_init).and_return(@generic_node)
158
+ end
159
+
160
+ after do
161
+ LightCloud.set(@key, @value)
162
+ end
163
+
164
+ it "should lookup the node or init for where to place key" do
165
+ LightCloud.should_receive(:locate_node_or_init).with(@key, anything).once.and_return(@generic_node)
166
+ end
167
+
168
+ it "should set the value on the node returned by locate node or init" do
169
+ @generic_node.should_receive(:set).with(@key, @value)
170
+ end
171
+ end
172
+
173
+ describe "getting" do
174
+ before do
175
+ @key = 'foo'
176
+ @value = 'baz'
177
+ @storage_ring.should_receive(:get_node).with(@key).and_return(@generic_node)
178
+ end
179
+
180
+ after do
181
+ LightCloud.get(@key).should eql(@value)
182
+ end
183
+
184
+ it "should not resort to the lookup table if it can find the key directly" do
185
+ @generic_node.should_receive(:get).with(@key).and_return(@value)
186
+
187
+ LightCloud.should_not_receive(:locate_node)
188
+ end
189
+
190
+ it "should get the storage node from the lookup table if it can't find the key directly" do
191
+ @generic_node.should_receive(:get).once.and_return(nil)
192
+
193
+ LightCloud.should_receive(:locate_node).with(@key, anything).and_return(@storage_valid_node)
194
+ @storage_valid_node.should_receive(:get).with(@key).and_return(@value)
195
+ end
196
+ end
197
+
198
+ describe "deleting" do
199
+ before do
200
+ @key = 'foo'
201
+ @value = 'baz'
202
+ end
203
+
204
+ after do
205
+ # I wrap this in a should_not raise error for the final
206
+ # spec in this context, which WOULD raise an error if
207
+ # it failed
208
+ lambda do
209
+ LightCloud.delete(@key)
210
+ end.should_not raise_error
211
+ end
212
+
213
+ it "should delete the key from first two lookup nodes from iteration" do
214
+ @nodes[0].should_receive(:delete).with(@key).once
215
+ @nodes[1].should_receive(:delete).with(@key).once
216
+ @nodes[2].should_not_receive(:delete)
217
+ end
218
+
219
+ it "should first try to get the storage node from lookup ring" do
220
+ LightCloud.should_receive(:locate_node).with(@key, anything).once.and_return(@generic_node)
221
+ LightCloud.should_not_receive(:get_storage_ring)
222
+
223
+ end
224
+
225
+ it "should try to get storage node directly if lookup ring failed" do
226
+ LightCloud.should_receive(:locate_node).with(@key, anything).once.and_return(nil)
227
+ LightCloud.should_receive(:get_storage_ring).once.and_return(@storage_ring)
228
+ @storage_ring.should_receive(:get_node).with(@key).once.and_return(@generic_node)
229
+
230
+ @generic_node.should_receive(:delete).with(@key)
231
+ end
232
+
233
+ it "should only delete from a storage node if one was found" do
234
+ LightCloud.should_receive(:locate_node).with(@key, anything).once.and_return(nil)
235
+ LightCloud.should_receive(:get_storage_ring).once.and_return(@storage_ring)
236
+ @storage_ring.should_receive(:get_node).with(@key).once.and_return(nil)
237
+ end
238
+ end
239
+ end
data/spec/spec_base.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Include the hash_ring library
2
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/lightcloud')
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mitchellh-lightcloud
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Mitchell Hashimoto
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-04 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rufus-tokyo
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.1.9
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mitchellh-hash_ring
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0.2"
34
+ version:
35
+ description: LightCloud library for Ruby
36
+ email: mitchell.hashimoto@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ - LICENSE
44
+ files:
45
+ - CREDITS
46
+ - LICENSE
47
+ - README.rdoc
48
+ - Rakefile
49
+ - lib/lightcloud.rb
50
+ - spec/spec_base.rb
51
+ - spec/lightcloud_spec.rb
52
+ has_rdoc: true
53
+ homepage: http://github.com/mitchellh/lightcloud/
54
+ post_install_message:
55
+ rdoc_options:
56
+ - --line-numbers
57
+ - --inline-source
58
+ - --title
59
+ - hash_ring
60
+ - --main
61
+ - README.rdoc
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.2.0
80
+ signing_key:
81
+ specification_version: 2
82
+ summary: LightCloud library for Ruby
83
+ test_files: []
84
+