redis_ring 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile.lock +9 -3
- data/config/redis.conf.erb +4 -6
- data/lib/redis_ring/application.rb +29 -29
- data/lib/redis_ring/background_thread.rb +45 -0
- data/lib/redis_ring/cli.rb +3 -6
- data/lib/redis_ring/configuration.rb +2 -1
- data/lib/redis_ring/http_client.rb +23 -0
- data/lib/redis_ring/master.rb +154 -0
- data/lib/redis_ring/master_rpc.rb +33 -0
- data/lib/redis_ring/node.rb +66 -0
- data/lib/redis_ring/process_manager.rb +33 -19
- data/lib/redis_ring/shard_config.rb +18 -0
- data/lib/redis_ring/slave.rb +62 -0
- data/lib/redis_ring/slave_rpc.rb +42 -0
- data/lib/redis_ring/version.rb +1 -1
- data/lib/redis_ring/web_interface.rb +63 -2
- data/lib/redis_ring/zookeeper_connection.rb +73 -0
- data/lib/redis_ring/zookeeper_observer.rb +47 -0
- data/lib/redis_ring.rb +11 -0
- data/redis_ring.gemspec +2 -0
- data/spec/cluster_builder.rb +224 -0
- data/spec/fakes/fake_http_client.rb +39 -0
- data/spec/fakes/fake_master_rpc.rb +20 -0
- data/spec/fakes/fake_node_provider.rb +2 -0
- data/spec/fakes/fake_process_manager.rb +19 -0
- data/spec/fakes/fake_slave_rpc.rb +36 -0
- data/spec/fakes/fake_zookeeper_connection.rb +2 -0
- data/spec/redis_ring/application_spec.rb +15 -13
- data/spec/redis_ring/master_rpc_spec.rb +20 -0
- data/spec/redis_ring/master_spec.rb +174 -0
- data/spec/redis_ring/node_spec.rb +53 -0
- data/spec/redis_ring/slave_rpc_spec.rb +46 -0
- data/spec/redis_ring/slave_spec.rb +81 -0
- data/spec/spec_helper.rb +20 -1
- data/spec/test.conf +3 -0
- metadata +54 -6
@@ -0,0 +1,42 @@
|
|
1
|
+
module RedisRing
|
2
|
+
class SlaveRPC
|
3
|
+
|
4
|
+
attr_reader :http_client
|
5
|
+
|
6
|
+
def initialize(http_client)
|
7
|
+
@http_client = http_client
|
8
|
+
end
|
9
|
+
|
10
|
+
def connection(host, port)
|
11
|
+
Connection.new(http_client, host, port)
|
12
|
+
end
|
13
|
+
|
14
|
+
class Connection
|
15
|
+
|
16
|
+
attr_reader :http_client, :host, :port
|
17
|
+
|
18
|
+
def initialize(http_client, host, port)
|
19
|
+
@http_client = http_client
|
20
|
+
@host = host
|
21
|
+
@port = port
|
22
|
+
end
|
23
|
+
|
24
|
+
def join
|
25
|
+
http_client.post(host, port, "/slave/join")
|
26
|
+
end
|
27
|
+
|
28
|
+
def status
|
29
|
+
JSON.parse(http_client.get(host, port, "/slave/status"))
|
30
|
+
end
|
31
|
+
|
32
|
+
def start_shard(shard_no)
|
33
|
+
http_client.post(host, port, "/slave/start_shard/#{shard_no}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop_shard(shard_no)
|
37
|
+
http_client.post(host, port, "/slave/stop_shard/#{shard_no}")
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/redis_ring/version.rb
CHANGED
@@ -1,14 +1,75 @@
|
|
1
1
|
module RedisRing
|
2
2
|
|
3
|
+
class WebInterfaceRunner
|
4
|
+
|
5
|
+
include RedisRing::BackgroundThread
|
6
|
+
|
7
|
+
attr_reader :master, :slave
|
8
|
+
|
9
|
+
def initialize(port, master, slave)
|
10
|
+
@port = port
|
11
|
+
@master = master
|
12
|
+
@slave = slave
|
13
|
+
end
|
14
|
+
|
15
|
+
def do_work
|
16
|
+
handler = Rack::Handler.get("webrick")
|
17
|
+
handler.run(WebInterface, :Port => @port, :master => @master, :slave => @slave) do |server|
|
18
|
+
@server = server
|
19
|
+
WebInterface.set :master, master
|
20
|
+
WebInterface.set :slave, slave
|
21
|
+
WebInterface.set :running, true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def halt
|
26
|
+
super
|
27
|
+
@server.stop if @server
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
3
32
|
class WebInterface < Sinatra::Base
|
4
33
|
|
34
|
+
def master
|
35
|
+
self.class.master
|
36
|
+
end
|
37
|
+
|
38
|
+
def slave
|
39
|
+
self.class.slave
|
40
|
+
end
|
41
|
+
|
5
42
|
get "/" do
|
6
43
|
"RedisRing is running"
|
7
44
|
end
|
8
45
|
|
9
|
-
|
46
|
+
post "/master/node_joined/:node_id" do
|
47
|
+
master.node_joined(params[:node_id])
|
48
|
+
"OK"
|
49
|
+
end
|
50
|
+
|
51
|
+
get "/slave/status" do
|
10
52
|
content_type :json
|
11
|
-
|
53
|
+
slave.status.to_json
|
54
|
+
end
|
55
|
+
|
56
|
+
post "/slave/join" do
|
57
|
+
slave.join
|
58
|
+
"OK"
|
59
|
+
end
|
60
|
+
|
61
|
+
post "/slave/start_shard/:shard_no" do
|
62
|
+
slave.start_shard(params[:shard_no].to_i)
|
63
|
+
"OK"
|
64
|
+
end
|
65
|
+
|
66
|
+
post "/slave/stop_shard/:shard_no" do
|
67
|
+
slave.stop_shard(params[:shard_no].to_i)
|
68
|
+
"OK"
|
69
|
+
end
|
70
|
+
|
71
|
+
class << self
|
72
|
+
attr_accessor :master, :slave
|
12
73
|
end
|
13
74
|
|
14
75
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module RedisRing
|
2
|
+
|
3
|
+
class ZookeeperConnection
|
4
|
+
|
5
|
+
attr_reader :current_node
|
6
|
+
|
7
|
+
def initialize(host_name, base_port, zookeeper_address)
|
8
|
+
@host_name = host_name
|
9
|
+
@base_port = base_port
|
10
|
+
@zookeeper_address = zookeeper_address
|
11
|
+
@connected = false
|
12
|
+
@base_path = "/nodes"
|
13
|
+
@mutex = Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def nodes_changed?
|
17
|
+
return true unless nodes_watcher
|
18
|
+
return nodes_watcher.completed?
|
19
|
+
end
|
20
|
+
|
21
|
+
def nodes
|
22
|
+
@nodes_watcher = Zookeeper::WatcherCallback.new
|
23
|
+
resp = zookeeper.get_children(:path => base_path, :watcher => nodes_watcher, :watcher_context => base_path)
|
24
|
+
return resp[:children].sort
|
25
|
+
end
|
26
|
+
|
27
|
+
def node_data(node)
|
28
|
+
resp = zookeeper.get(:path => "#{base_path}/#{node}")
|
29
|
+
data = resp[:data]
|
30
|
+
return data ? JSON.parse(data) : nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_status(status)
|
34
|
+
zookeeper.set(:path => "#{base_path}/status", :data => status.to_json)
|
35
|
+
end
|
36
|
+
|
37
|
+
def connected?
|
38
|
+
@connected
|
39
|
+
end
|
40
|
+
|
41
|
+
def connect
|
42
|
+
@mutex.synchronize do
|
43
|
+
break if connected?
|
44
|
+
|
45
|
+
@zookeeper = Zookeeper.new(zookeeper_address)
|
46
|
+
|
47
|
+
if @zookeeper.state != Zookeeper::ZOO_CONNECTED_STATE
|
48
|
+
raise "Zookeeper not connected!"
|
49
|
+
end
|
50
|
+
|
51
|
+
resp = @zookeeper.create(:path => base_path)
|
52
|
+
#raise "Could not create base path" unless resp[:rc] == Zookeeper::ZOK
|
53
|
+
|
54
|
+
resp = @zookeeper.create(:path => "#{base_path}/node-", :ephemeral => true, :sequence => true, :data => current_node_data.to_json)
|
55
|
+
#raise "Could not create node" unless resp[:rc] == Zookeeper::ZOK
|
56
|
+
|
57
|
+
@current_node = resp[:path].gsub("#{base_path}/", '')
|
58
|
+
|
59
|
+
@connected = true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
attr_reader :zookeeper, :base_path, :nodes_watcher, :zookeeper_address, :host_name, :base_port
|
66
|
+
|
67
|
+
def current_node_data
|
68
|
+
{:host => host_name, :port => base_port}
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RedisRing
|
2
|
+
|
3
|
+
class ZookeeperObserver
|
4
|
+
|
5
|
+
include RedisRing::BackgroundThread
|
6
|
+
|
7
|
+
attr_reader :master, :slave, :zookeeper_connection
|
8
|
+
|
9
|
+
def initialize(zookeeper_connection, master, slave)
|
10
|
+
@zookeeper_connection = zookeeper_connection
|
11
|
+
@master = master
|
12
|
+
@slave = slave
|
13
|
+
@current_master = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_work
|
17
|
+
on_node_list_changed(zookeeper_connection.nodes) if zookeeper_connection.nodes_changed?
|
18
|
+
sleep(0.1)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def on_node_list_changed(new_nodes)
|
24
|
+
current_master = new_nodes.first
|
25
|
+
|
26
|
+
unless @current_master == current_master
|
27
|
+
@current_master = current_master
|
28
|
+
|
29
|
+
if current_master == zookeeper_connection.current_node
|
30
|
+
master.became_master
|
31
|
+
else
|
32
|
+
master.no_longer_is_master
|
33
|
+
end
|
34
|
+
|
35
|
+
current_master_data = zookeeper_connection.node_data(current_master)
|
36
|
+
slave.current_master_host = current_master_data["host"]
|
37
|
+
slave.current_master_port = current_master_data["port"]
|
38
|
+
|
39
|
+
puts "NEW MASTER IS: #{slave.current_master_host}:#{slave.current_master_port}"
|
40
|
+
end
|
41
|
+
|
42
|
+
master.nodes_changed(new_nodes)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/redis_ring.rb
CHANGED
@@ -2,17 +2,28 @@ require 'socket'
|
|
2
2
|
require 'yaml'
|
3
3
|
require 'erb'
|
4
4
|
require 'fileutils'
|
5
|
+
require 'net/http'
|
5
6
|
|
6
7
|
require 'sinatra'
|
7
8
|
require 'json'
|
8
9
|
require 'daemons'
|
10
|
+
require 'zookeeper'
|
9
11
|
|
10
12
|
require 'monkey_patches'
|
11
13
|
|
14
|
+
require 'redis_ring/background_thread'
|
12
15
|
require 'redis_ring/configuration'
|
13
16
|
require 'redis_ring/shard_config'
|
14
17
|
require 'redis_ring/shard'
|
18
|
+
require 'redis_ring/http_client'
|
19
|
+
require 'redis_ring/node'
|
15
20
|
require 'redis_ring/application'
|
16
21
|
require 'redis_ring/web_interface'
|
17
22
|
require 'redis_ring/process_manager'
|
23
|
+
require 'redis_ring/master'
|
24
|
+
require 'redis_ring/master_rpc'
|
25
|
+
require 'redis_ring/slave'
|
26
|
+
require 'redis_ring/slave_rpc'
|
27
|
+
require 'redis_ring/zookeeper_observer'
|
28
|
+
require 'redis_ring/zookeeper_connection'
|
18
29
|
require 'redis_ring/cli'
|
data/redis_ring.gemspec
CHANGED
@@ -17,9 +17,11 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.add_dependency 'sinatra'
|
18
18
|
s.add_dependency 'json'
|
19
19
|
s.add_dependency 'daemons-mikehale'
|
20
|
+
s.add_dependency 'zookeeper'
|
20
21
|
|
21
22
|
s.add_development_dependency 'rspec'
|
22
23
|
s.add_development_dependency 'mocha'
|
24
|
+
s.add_development_dependency 'simplecov'
|
23
25
|
|
24
26
|
s.files = `git ls-files`.split("\n")
|
25
27
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -0,0 +1,224 @@
|
|
1
|
+
class ClusterBuilder
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
yield self
|
5
|
+
end
|
6
|
+
|
7
|
+
def nodes
|
8
|
+
@nodes ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def node(node_id)
|
12
|
+
nodes[node_id] ||= NodeBuilder.new(node_id, self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def node_ids
|
16
|
+
return nodes.keys.sort
|
17
|
+
end
|
18
|
+
|
19
|
+
def start_shard(node_id, shard_number)
|
20
|
+
start_shard_callbacks.each do |block|
|
21
|
+
block.call(node_id, shard_number)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop_shard(node_id, shard_number)
|
26
|
+
stop_shard_callbacks.each do |block|
|
27
|
+
block.call(node_id, shard_number)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_start_shard(&block)
|
32
|
+
start_shard_callbacks << block
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_stop_shard(&block)
|
36
|
+
stop_shard_callbacks << block
|
37
|
+
end
|
38
|
+
|
39
|
+
def start_shard_callbacks
|
40
|
+
@start_shard_callbacks ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
def stop_shard_callbacks
|
44
|
+
@stop_shard_callbacks ||= []
|
45
|
+
end
|
46
|
+
|
47
|
+
def fake_provider
|
48
|
+
@fake_provider ||= FakeNodeProvider.new(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def fake_connection
|
52
|
+
@fake_connection ||= FakeConnection.new(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
class FakeNodeProvider
|
56
|
+
def initialize(cluster_builder)
|
57
|
+
@cluster_builder = cluster_builder
|
58
|
+
@count = 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def new(host, port)
|
62
|
+
result = @cluster_builder.nodes.detect{|_, node| node.get.host == host && node.get.port == port }
|
63
|
+
return result[1].get if result
|
64
|
+
return @cluster_builder.node("unknown-node-#{@count += 1}").host(host).port(port).reachable(false).get
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class FakeConnection
|
69
|
+
def initialize(cluster_builder)
|
70
|
+
@cluster_builder = cluster_builder
|
71
|
+
end
|
72
|
+
|
73
|
+
def node_data(node_id)
|
74
|
+
return nil unless @cluster_builder.nodes.key?(node_id)
|
75
|
+
node = @cluster_builder.node(node_id).get
|
76
|
+
return {"host" => node.host, "port" => node.port}
|
77
|
+
end
|
78
|
+
|
79
|
+
def update_status(status)
|
80
|
+
@status = status
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class NodeBuilder
|
85
|
+
def initialize(node_id, cluster_builder)
|
86
|
+
@node_id = node_id
|
87
|
+
@joined = true
|
88
|
+
@running_shards = []
|
89
|
+
@available_shards = {}
|
90
|
+
@host = "localhost"
|
91
|
+
@port = 6400
|
92
|
+
@reachable = true
|
93
|
+
@cluster_builder = cluster_builder
|
94
|
+
end
|
95
|
+
|
96
|
+
def host(str)
|
97
|
+
@host = str
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
def port(int)
|
102
|
+
@port = int
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
def reachable(bool)
|
107
|
+
@reachable = bool
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
def joined(bool)
|
112
|
+
@joined = bool
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
def running_shards(arr)
|
117
|
+
@running_shards = arr
|
118
|
+
self
|
119
|
+
end
|
120
|
+
|
121
|
+
def running_shard(shard)
|
122
|
+
@running_shards << shard unless @running_shards.include?(shard)
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
def not_running_shard(shard)
|
127
|
+
@running_shards.delete(shard)
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
def available_shards(hash)
|
132
|
+
@available_shards = hash
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def available_shard(shard, timestamp)
|
137
|
+
@available_shards[shard] = timestamp
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
def not_available_shard(shard)
|
142
|
+
@available_shards.delete(shard)
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
def load_properties(node)
|
147
|
+
node.load(props)
|
148
|
+
end
|
149
|
+
|
150
|
+
def props
|
151
|
+
{
|
152
|
+
:host => @host,
|
153
|
+
:port => @port,
|
154
|
+
:node_id => @node_id,
|
155
|
+
:running_shards => @running_shards,
|
156
|
+
:available_shards => @available_shards,
|
157
|
+
:joined => @joined,
|
158
|
+
:reachable => @reachable,
|
159
|
+
:cluster_builder => @cluster_builder
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
def get
|
164
|
+
@node ||= FakeNode.new(self)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class FakeNode
|
169
|
+
|
170
|
+
attr_reader :host, :port, :node_id
|
171
|
+
|
172
|
+
def initialize(builder)
|
173
|
+
@builder = builder
|
174
|
+
@builder.load_properties(self)
|
175
|
+
end
|
176
|
+
|
177
|
+
def load(opts)
|
178
|
+
@joined = opts[:joined]
|
179
|
+
@running_shards = opts[:running_shards]
|
180
|
+
@available_shards = opts[:available_shards]
|
181
|
+
@reachable = opts[:reachable]
|
182
|
+
@host = opts[:host]
|
183
|
+
@port = opts[:port]
|
184
|
+
@node_id = opts[:node_id]
|
185
|
+
@cluster_builder = opts[:cluster_builder]
|
186
|
+
end
|
187
|
+
|
188
|
+
def joined?
|
189
|
+
@joined
|
190
|
+
end
|
191
|
+
|
192
|
+
def available_shards
|
193
|
+
@available_shards
|
194
|
+
end
|
195
|
+
|
196
|
+
def running_shards
|
197
|
+
@running_shards
|
198
|
+
end
|
199
|
+
|
200
|
+
def start_shard(shard_number)
|
201
|
+
ensure_reachable
|
202
|
+
@running_shards << shard_number unless @running_shards.include?(shard_number)
|
203
|
+
@cluster_builder.start_shard(@node_id, shard_number)
|
204
|
+
end
|
205
|
+
|
206
|
+
def stop_shard(shard_number)
|
207
|
+
ensure_reachable
|
208
|
+
@running_shards.delete(shard_number)
|
209
|
+
@cluster_builder.stop_shard(@node_id, shard_number)
|
210
|
+
end
|
211
|
+
|
212
|
+
def update_status!
|
213
|
+
@builder.load_properties(self)
|
214
|
+
ensure_reachable
|
215
|
+
end
|
216
|
+
|
217
|
+
protected
|
218
|
+
|
219
|
+
def ensure_reachable
|
220
|
+
raise "Node #{@node_id} #{@host}:#{@port} is not reachable!" unless @reachable
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class FakeHttpClient < RedisRing::HttpClient
|
2
|
+
|
3
|
+
def posts
|
4
|
+
@posts ||= []
|
5
|
+
end
|
6
|
+
|
7
|
+
def gets
|
8
|
+
@gets ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def responses
|
12
|
+
@responses ||= Hash.new("OK")
|
13
|
+
end
|
14
|
+
|
15
|
+
def post(host, port, path, params = {})
|
16
|
+
url = uri(host, port, path, params).to_s
|
17
|
+
posts << url
|
18
|
+
return responses[url]
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(host, port, path, params = {})
|
22
|
+
url = uri(host, port, path, params).to_s
|
23
|
+
gets << url
|
24
|
+
return responses[url]
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_response(url, text)
|
28
|
+
responses[url] = text
|
29
|
+
end
|
30
|
+
|
31
|
+
def sent_post?(url)
|
32
|
+
posts.include?(url)
|
33
|
+
end
|
34
|
+
|
35
|
+
def sent_get?(url)
|
36
|
+
gets.include?(url)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class FakeMasterRPC
|
2
|
+
|
3
|
+
def connection(host, port)
|
4
|
+
@connections ||= {}
|
5
|
+
@connections["#{host}:#{port}"] ||= Connection.new
|
6
|
+
end
|
7
|
+
|
8
|
+
class Connection
|
9
|
+
|
10
|
+
def nodes_loaded
|
11
|
+
@node_loaded ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def node_loaded(node_id)
|
15
|
+
nodes_loaded << node_id
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class FakeProcessManager
|
2
|
+
|
3
|
+
def started_shards
|
4
|
+
@started_shards ||= []
|
5
|
+
end
|
6
|
+
|
7
|
+
def start_shard(shard)
|
8
|
+
started_shards << shard
|
9
|
+
end
|
10
|
+
|
11
|
+
def stopped_shards
|
12
|
+
@stopped_shards ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def stop_shard(shard)
|
16
|
+
stopped_shards << shard
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class FakeSlaveRPC
|
2
|
+
|
3
|
+
def connection(host, port)
|
4
|
+
@connections ||= {}
|
5
|
+
@connections["#{host}:#{port}"] ||= Connection.new
|
6
|
+
end
|
7
|
+
|
8
|
+
class Connection
|
9
|
+
|
10
|
+
def status=(val)
|
11
|
+
@status = val
|
12
|
+
end
|
13
|
+
|
14
|
+
def status
|
15
|
+
@status ||= {"joined" => false, "running_shards" => [], "available_shards" => {}}
|
16
|
+
end
|
17
|
+
|
18
|
+
def started_shards
|
19
|
+
@started_shards ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_shard(shard_number)
|
23
|
+
started_shards << shard_number
|
24
|
+
end
|
25
|
+
|
26
|
+
def stopped_shards
|
27
|
+
@stopped_shards ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop_shard(shard_number)
|
31
|
+
stopped_shards << shard_number
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -2,22 +2,24 @@ require File.expand_path("../../spec_helper", __FILE__)
|
|
2
2
|
|
3
3
|
describe RedisRing::Application do
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
before(:each) do
|
6
|
+
@conf = RedisRing::Configuration.from_yml_file(File.expand_path("../../test.conf", __FILE__))
|
7
|
+
@app = RedisRing::Application.new(@conf)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should run shards" do
|
11
|
+
@app.start
|
12
|
+
sleep(1)
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
+
@app.slave.join
|
15
|
+
sleep(3)
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
+
@app.slave.running_shards.keys.sort.should == [0, 1, 2, 3]
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
after(:each) do
|
21
|
+
@app.stop
|
22
|
+
@app.wait
|
21
23
|
end
|
22
24
|
|
23
25
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe RedisRing::MasterRPC do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@http_client = FakeHttpClient.new
|
7
|
+
@host = "example.com"
|
8
|
+
@port = 666
|
9
|
+
@rpc = RedisRing::MasterRPC.new(@http_client).connection(@host, @port)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe :node_joined do
|
13
|
+
it "should post to node joined url" do
|
14
|
+
@rpc.node_loaded("some_node_id")
|
15
|
+
|
16
|
+
@http_client.sent_post?("http://example.com:666/master/node_joined/some_node_id").should be_true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|