mitchellh-lightcloud 0.6 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/lightcloud.rb +99 -76
  2. data/spec/lightcloud_spec.rb +53 -50
  3. 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.6'
38
+ VERSION = '0.7'
39
39
  DEFAULT_SYSTEM = 'default'
40
+ @@instance = nil
40
41
 
41
- @@systems = {}
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
- @systems = {}
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
- def self.init(lookup_nodes, storage_nodes, system=DEFAULT_SYSTEM, systems=@@systems)
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[system] = [lookup_ring, storage_ring, name_to_l_nodes, name_to_s_nodes]
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
- self.class.set(key, value, system, @systems)
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 = self.get_storage_ring(system, systems).get_node(key)
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 = self.locate_node(key, system, systems)
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
- # Lookup the key and delete it from both the storage ring
117
- # and lookup ring
118
- def self.delete(key, system=DEFAULT_SYSTEM, systems=@@systems)
119
- storage_node = self.locate_node(key, system, systems)
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, systems).get_node(key) if storage_node.nil?
122
- lookup_nodes = get_lookup_ring(system, systems).iterate_nodes(key)
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 self.locate_node_or_init(key, system, systems=@@systems)
137
- storage_node = self.locate_node(key, system, systems)
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 = self.get_storage_ring(system, systems).get_node(key)
121
+ storage_node = get_storage_ring(system).get_node(key)
141
122
 
142
- lookup_node = self.get_lookup_ring(system, systems).get_node(key)
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 self.locate_node(key, system=DEFAULT_SYSTEM, systems=@@systems)
153
- nodes = self.get_lookup_ring(system, systems).iterate_nodes(key)
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 self.get_storage_node(value, system, systems)
151
+ return get_storage_node(value, system)
171
152
  else
172
- return self._clean_up_ring(key, value, system, systems)
153
+ return _clean_up_ring(key, value, system)
173
154
  end
174
155
  end
175
156
 
176
- def self._clean_up_ring(key, value, system, systems=@@systems)
177
- nodes = self.get_lookup_ring(system, systems).iterate_nodes(key)
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 self.get_storage_node(value, system, systems)
171
+ return get_storage_node(value, system)
191
172
  end
192
173
 
193
174
  #--
194
175
  # Accessors for rings
195
176
  #++
196
- def self.get_lookup_ring(system=DEFAULT_SYSTEM, systems=@@systems)
197
- systems[system][0]
177
+ def get_lookup_ring(system=DEFAULT_SYSTEM)
178
+ @systems[system][0]
198
179
  end
199
180
 
200
- def self.get_storage_ring(system=DEFAULT_SYSTEM, systems=@@systems)
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 self.get_storage_node(name, system=DEFAULT_SYSTEM, systems=@@systems)
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
@@ -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.stub!(:get_lookup_ring).and_return(@lookup_ring)
38
- LightCloud.stub!(:get_storage_ring).and_return(@storage_ring)
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 = LightCloud.generate_ring(@valid_lookup_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 = LightCloud.generate_ring(@valid_lookup_nodes)
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
- LightCloud.should_receive(:locate_node).with(@key, anything, anything).once.and_return(@storage_node)
90
- LightCloud.locate_node_or_init(@key, LightCloud::DEFAULT_SYSTEM, anything)
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
- LightCloud.should_not_receive(:get_storage_ring)
95
- LightCloud.should_not_receive(:get_lookup_ring)
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
- LightCloud.should_receive(:get_storage_node).with(@storage_node, anything, anything).once
111
+ @cloud.should_receive(:get_storage_node).with(@storage_node, anything).once
110
112
 
111
- LightCloud.locate_node(@key)
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
- LightCloud.locate_node(@key).should be_nil
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
- LightCloud.should_not_receive(:get_storage_node)
121
- LightCloud.should_receive(:_clean_up_ring).with(@key, @storage_node, anything, anything).once
122
+ @cloud.should_not_receive(:get_storage_node)
123
+ @cloud.should_receive(:_clean_up_ring).with(@key, @storage_node, anything).once
122
124
 
123
- LightCloud.locate_node(@key)
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
- LightCloud._clean_up_ring(@key, @storage_node, LightCloud::DEFAULT_SYSTEM)
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
- LightCloud.should_receive(:get_storage_node).with(@storage_node, anything, anything).once
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
- LightCloud.stub!(:locate_node_or_init).and_return(@generic_node)
159
+ @cloud.stub!(:locate_node_or_init).and_return(@generic_node)
158
160
  end
159
161
 
160
162
  after do
161
- LightCloud.set(@key, @value)
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
- LightCloud.should_receive(:locate_node_or_init).with(@key, anything, anything).once.and_return(@generic_node)
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
- LightCloud.get(@key).should eql(@value)
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
- LightCloud.should_not_receive(:locate_node)
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
- LightCloud.should_receive(:locate_node).with(@key, anything, anything).and_return(@storage_valid_node)
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
- LightCloud.delete(@key)
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
- LightCloud.should_receive(:locate_node).with(@key, anything, anything).once.and_return(@generic_node)
221
- LightCloud.should_not_receive(:get_storage_ring)
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
- LightCloud.should_receive(:locate_node).with(@key, anything, anything).once.and_return(nil)
227
- LightCloud.should_receive(:get_storage_ring).once.and_return(@storage_ring)
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
- LightCloud.should_receive(:locate_node).with(@key, anything, anything).once.and_return(nil)
235
- LightCloud.should_receive(:get_storage_ring).once.and_return(@storage_ring)
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 "lightcloud instances" do
241
+ describe "class methods" do
241
242
  before do
242
- cloud_config = {
243
- 'lookup1_A' => ['127.0.0.1:1234', '127.0.0.2:1234'],
244
- 'storage1_A' => ['127.0.0.1:4567', '127.0.0.2:4567']
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 forward arguments to class method init on initialize" do
251
- LightCloud.should_receive(:init).with('a', 'b', 'c', {}).once
252
- LightCloud.new('a','b','c')
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 forward set arguments to class method" do
256
- LightCloud.should_receive(:set).with('key', 'value', anything, anything).once
257
- @cloud.set('key', 'value')
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 forward get arguments to class method" do
261
- LightCloud.should_receive(:get).with('key', anything, anything).once
262
- @cloud.get('key')
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 forward delete arguments to class method" do
266
- LightCloud.should_receive(:delete).with('key', anything, anything).once
267
- @cloud.delete('key')
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.6"
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
- - hash_ring
59
+ - lightcloud
60
60
  - --main
61
61
  - README.rdoc
62
62
  require_paths: