mitchellh-lightcloud 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CREDITS +9 -0
- data/LICENSE +10 -0
- data/README.rdoc +34 -0
- data/Rakefile +90 -0
- data/lib/lightcloud.rb +215 -0
- data/spec/lightcloud_spec.rb +239 -0
- data/spec/spec_base.rb +2 -0
- metadata +84 -0
data/CREDITS
ADDED
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
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
|
+
|