mitchellh-lightcloud 0.6 → 0.7
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/lib/lightcloud.rb +99 -76
- data/spec/lightcloud_spec.rb +53 -50
- metadata +2 -2
data/lib/lightcloud.rb
CHANGED
@@ -35,26 +35,30 @@ require File.join(File.dirname(__FILE__), 'tyrant_client')
|
|
35
35
|
# print LightCloud.get("hello") # => nil
|
36
36
|
#
|
37
37
|
class LightCloud
|
38
|
-
VERSION = '0.
|
38
|
+
VERSION = '0.7'
|
39
39
|
DEFAULT_SYSTEM = 'default'
|
40
|
+
@@instance = nil
|
40
41
|
|
41
|
-
|
42
|
-
|
42
|
+
#--
|
43
|
+
# INSTANCE METHODS
|
44
|
+
#++
|
43
45
|
# Initialize LightCloud as an instance instead of using the class
|
44
46
|
# methods. Expects the same arguments as LightCloud.init, except
|
45
47
|
# this will return a new instance of LightCloud.
|
46
48
|
#
|
47
49
|
# Any nodes initialized through here will not work one class methods.
|
48
|
-
def initialize(lookup_nodes, storage_nodes, system=DEFAULT_SYSTEM)
|
49
|
-
|
50
|
-
self.class.init(lookup_nodes, storage_nodes, system, @systems)
|
50
|
+
def initialize(lookup_nodes=nil, storage_nodes=nil, system=DEFAULT_SYSTEM)
|
51
|
+
add_system(lookup_nodes, storage_nodes, system) if !lookup_nodes.nil? && !storage_nodes.nil?
|
51
52
|
end
|
52
53
|
|
53
|
-
|
54
|
+
#
|
55
|
+
# Create a new LightCloud system within a pre-existing instance.
|
56
|
+
def add_system(lookup_nodes, storage_nodes, system=DEFAULT_SYSTEM)
|
54
57
|
lookup_ring, name_to_l_nodes = self.generate_ring(lookup_nodes)
|
55
58
|
storage_ring, name_to_s_nodes = self.generate_ring(storage_nodes)
|
56
59
|
|
57
|
-
systems
|
60
|
+
@systems ||= {}
|
61
|
+
@systems[system] = [lookup_ring, storage_ring, name_to_l_nodes, name_to_s_nodes]
|
58
62
|
end
|
59
63
|
|
60
64
|
#--
|
@@ -63,63 +67,40 @@ class LightCloud
|
|
63
67
|
# Sets a value to a key on a LightCloud instance. See
|
64
68
|
# LightCloud.set for more information
|
65
69
|
def set(key, value, system=DEFAULT_SYSTEM)
|
66
|
-
|
70
|
+
storage_node = locate_node_or_init(key, system)
|
71
|
+
return storage_node.set(key, value)
|
67
72
|
end
|
68
73
|
|
69
74
|
#
|
70
75
|
# Gets a value to a key on a LightCloud instance. See
|
71
76
|
# LightCloud.get for more information.
|
72
77
|
def get(key, system=DEFAULT_SYSTEM)
|
73
|
-
self.class.get(key, system, @systems)
|
74
|
-
end
|
75
|
-
|
76
|
-
#
|
77
|
-
# Delete a value from a LightCloud instance. See
|
78
|
-
# LightCloud.delete for more information.
|
79
|
-
def delete(key, system=DEFAULT_SYSTEM)
|
80
|
-
self.class.delete(key, system, @systems)
|
81
|
-
end
|
82
|
-
|
83
|
-
#
|
84
|
-
# Sets a value to a key in the LightCloud system.
|
85
|
-
#
|
86
|
-
# Set first checks to see if the key is already stored. If it is
|
87
|
-
# it uses that same node to store the new value. Otherwise, it
|
88
|
-
# determines where to store the value based on the hash_ring
|
89
|
-
def self.set(key, value, system=DEFAULT_SYSTEM, systems=@@systems)
|
90
|
-
storage_node = self.locate_node_or_init(key, system, systems)
|
91
|
-
return storage_node.set(key, value)
|
92
|
-
end
|
93
|
-
|
94
|
-
#
|
95
|
-
# Gets a value based on a key.
|
96
|
-
def self.get(key, system=DEFAULT_SYSTEM, systems=@@systems)
|
97
78
|
result = nil
|
98
79
|
|
99
80
|
# Try to lookup key directly
|
100
|
-
storage_node =
|
81
|
+
storage_node = get_storage_ring(system).get_node(key)
|
101
82
|
value = storage_node.get(key)
|
102
83
|
|
103
84
|
result = value unless value.nil?
|
104
85
|
|
105
86
|
# Else use the lookup ring
|
106
87
|
if result.nil?
|
107
|
-
storage_node =
|
88
|
+
storage_node = locate_node(key, system)
|
108
89
|
|
109
90
|
result = storage_node.get(key) unless storage_node.nil?
|
110
91
|
end
|
111
92
|
|
112
|
-
result
|
93
|
+
result
|
113
94
|
end
|
114
95
|
|
115
96
|
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
def
|
119
|
-
storage_node =
|
97
|
+
# Delete a value from a LightCloud instance. See
|
98
|
+
# LightCloud.delete for more information.
|
99
|
+
def delete(key, system=DEFAULT_SYSTEM)
|
100
|
+
storage_node = locate_node(key, system)
|
120
101
|
|
121
|
-
storage_node = get_storage_ring(system
|
122
|
-
lookup_nodes = get_lookup_ring(system
|
102
|
+
storage_node = get_storage_ring(system).get_node(key) if storage_node.nil?
|
103
|
+
lookup_nodes = get_lookup_ring(system).iterate_nodes(key)
|
123
104
|
lookup_nodes.each_index do |i|
|
124
105
|
break if i > 1
|
125
106
|
|
@@ -133,13 +114,13 @@ class LightCloud
|
|
133
114
|
#--
|
134
115
|
# Lookup Cloud
|
135
116
|
#++
|
136
|
-
def
|
137
|
-
storage_node =
|
117
|
+
def locate_node_or_init(key, system)
|
118
|
+
storage_node = locate_node(key, system)
|
138
119
|
|
139
120
|
if storage_node.nil?
|
140
|
-
storage_node =
|
121
|
+
storage_node = get_storage_ring(system).get_node(key)
|
141
122
|
|
142
|
-
lookup_node =
|
123
|
+
lookup_node = get_lookup_ring(system).get_node(key)
|
143
124
|
lookup_node.set(key, storage_node.to_s)
|
144
125
|
end
|
145
126
|
|
@@ -149,8 +130,8 @@ class LightCloud
|
|
149
130
|
#
|
150
131
|
# Locates a node in the lookup ring, returning the node if it is found, or
|
151
132
|
# nil otherwise.
|
152
|
-
def
|
153
|
-
nodes =
|
133
|
+
def locate_node(key, system=DEFAULT_SYSTEM)
|
134
|
+
nodes = get_lookup_ring(system).iterate_nodes(key)
|
154
135
|
|
155
136
|
lookups = 0
|
156
137
|
value = nil
|
@@ -167,14 +148,14 @@ class LightCloud
|
|
167
148
|
return nil if value.nil?
|
168
149
|
|
169
150
|
if lookups == 0
|
170
|
-
return
|
151
|
+
return get_storage_node(value, system)
|
171
152
|
else
|
172
|
-
return
|
153
|
+
return _clean_up_ring(key, value, system)
|
173
154
|
end
|
174
155
|
end
|
175
156
|
|
176
|
-
def
|
177
|
-
nodes =
|
157
|
+
def _clean_up_ring(key, value, system)
|
158
|
+
nodes = get_lookup_ring(system).iterate_nodes(key)
|
178
159
|
|
179
160
|
nodes.each_index do |i|
|
180
161
|
break if i > 1
|
@@ -187,25 +168,84 @@ class LightCloud
|
|
187
168
|
end
|
188
169
|
end
|
189
170
|
|
190
|
-
return
|
171
|
+
return get_storage_node(value, system)
|
191
172
|
end
|
192
173
|
|
193
174
|
#--
|
194
175
|
# Accessors for rings
|
195
176
|
#++
|
196
|
-
def
|
197
|
-
systems[system][0]
|
177
|
+
def get_lookup_ring(system=DEFAULT_SYSTEM)
|
178
|
+
@systems[system][0]
|
198
179
|
end
|
199
180
|
|
200
|
-
def
|
201
|
-
systems[system][1]
|
181
|
+
def get_storage_ring(system=DEFAULT_SYSTEM)
|
182
|
+
@systems[system][1]
|
202
183
|
end
|
203
184
|
|
204
185
|
#--
|
205
186
|
# Accessors for nodes
|
206
187
|
#++
|
207
|
-
def
|
208
|
-
systems[system][3][name]
|
188
|
+
def get_storage_node(name, system=DEFAULT_SYSTEM)
|
189
|
+
@systems[system][3][name]
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# Given a set of nodes it creates the the nodes as Tokyo
|
194
|
+
# Tyrant objects and returns a hash ring with them
|
195
|
+
def generate_ring(nodes)
|
196
|
+
objects = []
|
197
|
+
name_to_obj = {}
|
198
|
+
|
199
|
+
nodes.each do |name, nodelist|
|
200
|
+
obj = TyrantNode.new(name, nodelist)
|
201
|
+
name_to_obj[name] = obj
|
202
|
+
|
203
|
+
objects.push(obj)
|
204
|
+
end
|
205
|
+
|
206
|
+
return HashRing.new(objects), name_to_obj
|
207
|
+
end
|
208
|
+
|
209
|
+
#--
|
210
|
+
# CLASS METHODS
|
211
|
+
#++
|
212
|
+
#
|
213
|
+
# Initializes LightCloud library with lookup and storage nodes.
|
214
|
+
# This only needs to be called with servers with which you intend
|
215
|
+
# to use the class methods (set/get/delete)
|
216
|
+
def self.init(lookup_nodes, storage_nodes, system=DEFAULT_SYSTEM)
|
217
|
+
instance.add_system(lookup_nodes, storage_nodes, system)
|
218
|
+
end
|
219
|
+
|
220
|
+
#
|
221
|
+
# Sets a value to a key in the LightCloud system.
|
222
|
+
#
|
223
|
+
# Set first checks to see if the key is already stored. If it is
|
224
|
+
# it uses that same node to store the new value. Otherwise, it
|
225
|
+
# determines where to store the value based on the hash_ring
|
226
|
+
def self.set(key, value, system=DEFAULT_SYSTEM)
|
227
|
+
instance.set(key, value, system)
|
228
|
+
end
|
229
|
+
|
230
|
+
#
|
231
|
+
# Gets a value based on a key.
|
232
|
+
def self.get(key, system=DEFAULT_SYSTEM)
|
233
|
+
instance.get(key, system)
|
234
|
+
end
|
235
|
+
|
236
|
+
#
|
237
|
+
# Lookup the key and delete it from both the storage ring
|
238
|
+
# and lookup ring
|
239
|
+
def self.delete(key, system=DEFAULT_SYSTEM)
|
240
|
+
instance.delete(key, system)
|
241
|
+
end
|
242
|
+
|
243
|
+
#--
|
244
|
+
# Instance accessor
|
245
|
+
#++
|
246
|
+
def self.instance
|
247
|
+
@@instance ||= self.new
|
248
|
+
@@instance
|
209
249
|
end
|
210
250
|
|
211
251
|
#--
|
@@ -225,21 +265,4 @@ class LightCloud
|
|
225
265
|
|
226
266
|
return lookup_nodes, storage_nodes
|
227
267
|
end
|
228
|
-
|
229
|
-
#
|
230
|
-
# Given a set of nodes it creates the the nodes as Tokyo
|
231
|
-
# Tyrant objects and returns a hash ring with them
|
232
|
-
def self.generate_ring(nodes)
|
233
|
-
objects = []
|
234
|
-
name_to_obj = {}
|
235
|
-
|
236
|
-
nodes.each do |name, nodelist|
|
237
|
-
obj = TyrantNode.new(name, nodelist)
|
238
|
-
name_to_obj[name] = obj
|
239
|
-
|
240
|
-
objects.push(obj)
|
241
|
-
end
|
242
|
-
|
243
|
-
return HashRing.new(objects), name_to_obj
|
244
|
-
end
|
245
268
|
end
|
data/spec/lightcloud_spec.rb
CHANGED
@@ -33,9 +33,11 @@ describe LightCloud do
|
|
33
33
|
|
34
34
|
@storage_ring = mock(HashRing)
|
35
35
|
@storage_ring.stub!(:get_node).and_return(@storage_valid_node)
|
36
|
-
|
37
|
-
LightCloud.
|
38
|
-
|
36
|
+
|
37
|
+
@cloud = LightCloud.new
|
38
|
+
@cloud.stub!(:get_lookup_ring).and_return(@lookup_ring)
|
39
|
+
@cloud.stub!(:get_storage_ring).and_return(@storage_ring)
|
40
|
+
@cloud.stub!(:get_storage_node).and_return(@storage_valid_node)
|
39
41
|
end
|
40
42
|
|
41
43
|
describe "node generation" do
|
@@ -65,14 +67,14 @@ describe LightCloud do
|
|
65
67
|
end
|
66
68
|
|
67
69
|
it "should return the tyrant nodes as a name to node hash" do
|
68
|
-
unneeded, name_to_nodes =
|
70
|
+
unneeded, name_to_nodes = @cloud.generate_ring(@valid_lookup_nodes)
|
69
71
|
|
70
72
|
name_to_nodes.should be_has_key('lookup1_A')
|
71
73
|
name_to_nodes['lookup1_A'].should be_kind_of(TyrantNode)
|
72
74
|
end
|
73
75
|
|
74
76
|
it "should return a hash ring with the nodes" do
|
75
|
-
ring, unneeded =
|
77
|
+
ring, unneeded = @cloud.generate_ring(@valid_lookup_nodes)
|
76
78
|
|
77
79
|
ring.should be_kind_of(HashRing)
|
78
80
|
end
|
@@ -86,13 +88,13 @@ describe LightCloud do
|
|
86
88
|
|
87
89
|
describe "locating or initting a storage node by key" do
|
88
90
|
after do
|
89
|
-
|
90
|
-
|
91
|
+
@cloud.should_receive(:locate_node).with(@key, anything).once.and_return(@storage_node)
|
92
|
+
@cloud.locate_node_or_init(@key, LightCloud::DEFAULT_SYSTEM)
|
91
93
|
end
|
92
94
|
|
93
95
|
it "should just return the storage node if it was found" do
|
94
|
-
|
95
|
-
|
96
|
+
@cloud.should_not_receive(:get_storage_ring)
|
97
|
+
@cloud.should_not_receive(:get_lookup_ring)
|
96
98
|
end
|
97
99
|
|
98
100
|
it "should set the lookup ring to point to the new storage node if no previous storage node was found" do
|
@@ -106,27 +108,27 @@ describe LightCloud do
|
|
106
108
|
describe "locating a storage node by key" do
|
107
109
|
it "should return the storage node if the key is found in the lookup ring" do
|
108
110
|
@nodes[0].should_receive(:get).with(@key).and_return(@storage_node)
|
109
|
-
|
111
|
+
@cloud.should_receive(:get_storage_node).with(@storage_node, anything).once
|
110
112
|
|
111
|
-
|
113
|
+
@cloud.locate_node(@key)
|
112
114
|
end
|
113
115
|
|
114
116
|
it "should return nil if the key doesn't exist in the lookup ring" do
|
115
|
-
|
117
|
+
@cloud.locate_node(@key).should be_nil
|
116
118
|
end
|
117
119
|
|
118
120
|
it "should attempt to clean up the lookup ring if the value is NOT found in the first node" do
|
119
121
|
@nodes[1].should_receive(:get).with(@key).and_return(@storage_node)
|
120
|
-
|
121
|
-
|
122
|
+
@cloud.should_not_receive(:get_storage_node)
|
123
|
+
@cloud.should_receive(:_clean_up_ring).with(@key, @storage_node, anything).once
|
122
124
|
|
123
|
-
|
125
|
+
@cloud.locate_node(@key)
|
124
126
|
end
|
125
127
|
end
|
126
128
|
|
127
129
|
describe "cleaning the lookup ring" do
|
128
130
|
after do
|
129
|
-
|
131
|
+
@cloud._clean_up_ring(@key, @storage_node, LightCloud::DEFAULT_SYSTEM)
|
130
132
|
end
|
131
133
|
|
132
134
|
it "should set the key/value onto the first node (index 0)" do
|
@@ -144,7 +146,7 @@ describe LightCloud do
|
|
144
146
|
end
|
145
147
|
|
146
148
|
it "should return the storage node lookup" do
|
147
|
-
|
149
|
+
@cloud.should_receive(:get_storage_node).with(@storage_node, anything).once
|
148
150
|
end
|
149
151
|
end
|
150
152
|
end
|
@@ -154,15 +156,15 @@ describe LightCloud do
|
|
154
156
|
@key = 'hello'
|
155
157
|
@value = 'world!'
|
156
158
|
|
157
|
-
|
159
|
+
@cloud.stub!(:locate_node_or_init).and_return(@generic_node)
|
158
160
|
end
|
159
161
|
|
160
162
|
after do
|
161
|
-
|
163
|
+
@cloud.set(@key, @value)
|
162
164
|
end
|
163
165
|
|
164
166
|
it "should lookup the node or init for where to place key" do
|
165
|
-
|
167
|
+
@cloud.should_receive(:locate_node_or_init).with(@key, anything).once.and_return(@generic_node)
|
166
168
|
end
|
167
169
|
|
168
170
|
it "should set the value on the node returned by locate node or init" do
|
@@ -178,19 +180,19 @@ describe LightCloud do
|
|
178
180
|
end
|
179
181
|
|
180
182
|
after do
|
181
|
-
|
183
|
+
@cloud.get(@key).should eql(@value)
|
182
184
|
end
|
183
185
|
|
184
186
|
it "should not resort to the lookup table if it can find the key directly" do
|
185
187
|
@generic_node.should_receive(:get).with(@key).and_return(@value)
|
186
188
|
|
187
|
-
|
189
|
+
@cloud.should_not_receive(:locate_node)
|
188
190
|
end
|
189
191
|
|
190
192
|
it "should get the storage node from the lookup table if it can't find the key directly" do
|
191
193
|
@generic_node.should_receive(:get).once.and_return(nil)
|
192
194
|
|
193
|
-
|
195
|
+
@cloud.should_receive(:locate_node).with(@key, anything).and_return(@storage_valid_node)
|
194
196
|
@storage_valid_node.should_receive(:get).with(@key).and_return(@value)
|
195
197
|
end
|
196
198
|
end
|
@@ -206,7 +208,7 @@ describe LightCloud do
|
|
206
208
|
# spec in this context, which WOULD raise an error if
|
207
209
|
# it failed
|
208
210
|
lambda do
|
209
|
-
|
211
|
+
@cloud.delete(@key)
|
210
212
|
end.should_not raise_error
|
211
213
|
end
|
212
214
|
|
@@ -217,54 +219,55 @@ describe LightCloud do
|
|
217
219
|
end
|
218
220
|
|
219
221
|
it "should first try to get the storage node from lookup ring" do
|
220
|
-
|
221
|
-
|
222
|
-
|
222
|
+
@cloud.should_receive(:locate_node).with(@key, anything).once.and_return(@generic_node)
|
223
|
+
@cloud.should_not_receive(:get_storage_ring)
|
223
224
|
end
|
224
225
|
|
225
226
|
it "should try to get storage node directly if lookup ring failed" do
|
226
|
-
|
227
|
-
|
227
|
+
@cloud.should_receive(:locate_node).with(@key, anything).once.and_return(nil)
|
228
|
+
@cloud.should_receive(:get_storage_ring).once.and_return(@storage_ring)
|
228
229
|
@storage_ring.should_receive(:get_node).with(@key).once.and_return(@generic_node)
|
229
230
|
|
230
231
|
@generic_node.should_receive(:delete).with(@key)
|
231
232
|
end
|
232
233
|
|
233
234
|
it "should only delete from a storage node if one was found" do
|
234
|
-
|
235
|
-
|
235
|
+
@cloud.should_receive(:locate_node).with(@key, anything).once.and_return(nil)
|
236
|
+
@cloud.should_receive(:get_storage_ring).once.and_return(@storage_ring)
|
236
237
|
@storage_ring.should_receive(:get_node).with(@key).once.and_return(nil)
|
237
238
|
end
|
238
239
|
end
|
239
240
|
|
240
|
-
describe "
|
241
|
+
describe "class methods" do
|
241
242
|
before do
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
@cloud = LightCloud.new(*LightCloud.generate_nodes(cloud_config))
|
243
|
+
LightCloud.should_receive(:instance).and_return(@cloud)
|
244
|
+
|
245
|
+
@key = 'foo'
|
246
|
+
@value = 'bar'
|
248
247
|
end
|
249
248
|
|
250
|
-
it "should
|
251
|
-
|
252
|
-
|
249
|
+
it "should call add_system on singleton when init is called" do
|
250
|
+
@cloud.should_receive(:add_system).with(@valid_lookup_nodes, @valid_storage_nodes, anything)
|
251
|
+
|
252
|
+
LightCloud.init(@valid_lookup_nodes, @valid_storage_nodes)
|
253
253
|
end
|
254
254
|
|
255
|
-
it "should
|
256
|
-
|
257
|
-
|
255
|
+
it "should call get on instance for get" do
|
256
|
+
@cloud.should_receive(:get).with(@key, anything).once
|
257
|
+
|
258
|
+
LightCloud.get(@key)
|
258
259
|
end
|
259
260
|
|
260
|
-
it "should
|
261
|
-
|
262
|
-
|
261
|
+
it "should call set on instance for set" do
|
262
|
+
@cloud.should_receive(:set).with(@key, @value, anything).once
|
263
|
+
|
264
|
+
LightCloud.set(@key, @value)
|
263
265
|
end
|
264
266
|
|
265
|
-
it "should
|
266
|
-
|
267
|
-
|
267
|
+
it "should call delete on instance for delete" do
|
268
|
+
@cloud.should_receive(:delete).with(@key, anything).once
|
269
|
+
|
270
|
+
LightCloud.delete(@key)
|
268
271
|
end
|
269
272
|
end
|
270
273
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mitchellh-lightcloud
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.7"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mitchell Hashimoto
|
@@ -56,7 +56,7 @@ rdoc_options:
|
|
56
56
|
- --line-numbers
|
57
57
|
- --inline-source
|
58
58
|
- --title
|
59
|
-
-
|
59
|
+
- lightcloud
|
60
60
|
- --main
|
61
61
|
- README.rdoc
|
62
62
|
require_paths:
|