stn-dcell 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +7 -0
- data/.rspec +4 -0
- data/.travis.yml +30 -0
- data/CHANGES.md +53 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +20 -0
- data/README.md +168 -0
- data/Rakefile +4 -0
- data/benchmarks/messaging.rb +73 -0
- data/benchmarks/receiver.rb +37 -0
- data/dcell.gemspec +29 -0
- data/examples/itchy.rb +26 -0
- data/examples/scratchy.rb +12 -0
- data/explorer/css/bootstrap-responsive.css +686 -0
- data/explorer/css/bootstrap-responsive.min.css +12 -0
- data/explorer/css/bootstrap.css +3990 -0
- data/explorer/css/bootstrap.min.css +689 -0
- data/explorer/css/explorer.css +28 -0
- data/explorer/ico/favicon.ico +0 -0
- data/explorer/img/glyphicons-halflings-white.png +0 -0
- data/explorer/img/glyphicons-halflings.png +0 -0
- data/explorer/img/logo.png +0 -0
- data/explorer/index.html.erb +94 -0
- data/explorer/js/bootstrap.js +1726 -0
- data/explorer/js/bootstrap.min.js +6 -0
- data/lib/dcell.rb +127 -0
- data/lib/dcell/actor_proxy.rb +30 -0
- data/lib/dcell/celluloid_ext.rb +120 -0
- data/lib/dcell/directory.rb +31 -0
- data/lib/dcell/explorer.rb +74 -0
- data/lib/dcell/future_proxy.rb +32 -0
- data/lib/dcell/global.rb +23 -0
- data/lib/dcell/info_service.rb +122 -0
- data/lib/dcell/mailbox_proxy.rb +53 -0
- data/lib/dcell/messages.rb +65 -0
- data/lib/dcell/node.rb +138 -0
- data/lib/dcell/node_manager.rb +79 -0
- data/lib/dcell/registries/cassandra_adapter.rb +126 -0
- data/lib/dcell/registries/mongodb_adapter.rb +85 -0
- data/lib/dcell/registries/redis_adapter.rb +95 -0
- data/lib/dcell/registries/zk_adapter.rb +123 -0
- data/lib/dcell/responses.rb +16 -0
- data/lib/dcell/router.rb +46 -0
- data/lib/dcell/rpc.rb +95 -0
- data/lib/dcell/rspec.rb +1 -0
- data/lib/dcell/server.rb +73 -0
- data/lib/dcell/version.rb +3 -0
- data/logo.png +0 -0
- data/spec/dcell/actor_proxy_spec.rb +72 -0
- data/spec/dcell/celluloid_ext_spec.rb +32 -0
- data/spec/dcell/directory_spec.rb +22 -0
- data/spec/dcell/explorer_spec.rb +17 -0
- data/spec/dcell/global_spec.rb +25 -0
- data/spec/dcell/node_spec.rb +23 -0
- data/spec/dcell/registries/mongodb_adapter_spec.rb +7 -0
- data/spec/dcell/registries/redis_adapter_spec.rb +6 -0
- data/spec/dcell/registries/zk_adapter_spec.rb +28 -0
- data/spec/options/01-options.rb +10 -0
- data/spec/options/02-registry.rb +13 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/helpers.rb +46 -0
- data/spec/support/registry_examples.rb +35 -0
- data/spec/test_node.rb +38 -0
- data/tasks/cassandra.task +84 -0
- data/tasks/rspec.task +7 -0
- data/tasks/zookeeper.task +58 -0
- metadata +239 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
module DCell
|
4
|
+
class InfoService
|
5
|
+
include Celluloid
|
6
|
+
attr_reader :os, :os_version, :hostname, :platform, :distribution
|
7
|
+
attr_reader :cpu_arch, :cpu_type, :cpu_vendor, :cpu_speed, :cpu_count
|
8
|
+
attr_reader :ruby_version, :ruby_engine, :ruby_platform
|
9
|
+
|
10
|
+
UPTIME_REGEX = /up ((\d+ days?,)?\s*(\d+:\d+|\d+ \w+)),.*(( \d.\d{2},?){3})/
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@cpu_arch = RbConfig::CONFIG['host_cpu']
|
14
|
+
@os = RbConfig::CONFIG['host_os'][/^[A-Za-z]+/]
|
15
|
+
|
16
|
+
uname = `uname -a`.match(/\w+ (\w[\w+\.\-]*) ([\w+\.\-]+)/)
|
17
|
+
@hostname, @os_version = uname[1], uname[2]
|
18
|
+
|
19
|
+
@platform = RUBY_PLATFORM
|
20
|
+
@ruby_version = RUBY_VERSION
|
21
|
+
@ruby_engine = RUBY_ENGINE
|
22
|
+
|
23
|
+
case os
|
24
|
+
when 'darwin'
|
25
|
+
cpu_info = `sysctl -n machdep.cpu.brand_string`.match(/^((\w+).*) @ (\d+.\d+)GHz/)
|
26
|
+
if cpu_info
|
27
|
+
@cpu_type = cpu_info[1]
|
28
|
+
@cpu_vendor = cpu_info[2].downcase.to_sym
|
29
|
+
@cpu_speed = Float(cpu_info[3])
|
30
|
+
end
|
31
|
+
|
32
|
+
@cpu_count = Integer(`sysctl hw.ncpu`[/\d+/])
|
33
|
+
os, release, build = `sw_vers`.scan(/:\t(.*)$/).flatten
|
34
|
+
@distribution = "#{os} #{release} (#{build})"
|
35
|
+
when 'linux'
|
36
|
+
cpu_info = File.read("/proc/cpuinfo")
|
37
|
+
|
38
|
+
@cpu_vendor = cpu_info[/vendor_id:\s+\s+(Genuine)?(\w+)/, 2]
|
39
|
+
model_name = cpu_info.match(/model name\s+:\s+((\w+).*) @ (\d+.\d+)GHz/)
|
40
|
+
if model_name
|
41
|
+
@cpu_type = model_name[1].gsub(/\s+/, ' ')
|
42
|
+
@cpu_vendor = model_name[2].downcase.to_sym
|
43
|
+
@cpu_speed = Float(model_name[3])
|
44
|
+
end
|
45
|
+
|
46
|
+
cores = cpu_info.scan(/core id\s+: \d+/).uniq.size
|
47
|
+
@cpu_count = cores > 0 ? cores : 1
|
48
|
+
@distribution = discover_distribution
|
49
|
+
else
|
50
|
+
@cpu_type = @cpu_vendor = @cpu_speed = @cpu_count = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
case RUBY_ENGINE
|
54
|
+
when 'ruby'
|
55
|
+
@ruby_platform = "ruby #{RUBY_VERSION}"
|
56
|
+
when 'jruby'
|
57
|
+
@ruby_platform = "jruby #{JRUBY_VERSION}"
|
58
|
+
when 'rbx'
|
59
|
+
@ruby_platform = "rbx #{Rubinius::VERSION}"
|
60
|
+
else
|
61
|
+
@ruby_platform = RUBY_ENGINE
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def platform; RUBY_PLATFORM; end
|
66
|
+
def ruby_engine; RUBY_ENGINE; end
|
67
|
+
def ruby_version; RUBY_VERSION; end
|
68
|
+
|
69
|
+
def load_averages(uptime_string = `uptime`)
|
70
|
+
matches = uptime_string.match(UPTIME_REGEX)
|
71
|
+
unless matches
|
72
|
+
Logger.warn "Couldn't parse uptime output: #{uptime_string}"
|
73
|
+
return
|
74
|
+
end
|
75
|
+
|
76
|
+
averages = matches[4].strip
|
77
|
+
averages.split(/,? /).map(&:to_f)
|
78
|
+
end
|
79
|
+
alias_method :load_average, :load_averages
|
80
|
+
|
81
|
+
def uptime(uptime_string = `uptime`)
|
82
|
+
matches = uptime_string.match(UPTIME_REGEX)
|
83
|
+
unless matches
|
84
|
+
Logger.warn "Couldn't parse uptime output: #{uptime_string}"
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
88
|
+
uptime = matches[1]
|
89
|
+
days_string = uptime[/^(\d+) days/, 1]
|
90
|
+
days_string ? Integer(days_string) : 0
|
91
|
+
end
|
92
|
+
|
93
|
+
def discover_distribution
|
94
|
+
`lsb_release -d`[/Description:\s+(.*)\s*$/, 1]
|
95
|
+
rescue Errno::ENOENT
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_hash
|
99
|
+
uptime_string = `uptime`
|
100
|
+
|
101
|
+
{
|
102
|
+
:os => os,
|
103
|
+
:os_version => os_version,
|
104
|
+
:hostname => hostname,
|
105
|
+
:platform => platform,
|
106
|
+
:distribution => distribution,
|
107
|
+
:ruby_version => ruby_version,
|
108
|
+
:ruby_engine => ruby_engine,
|
109
|
+
:ruby_platform => ruby_platform,
|
110
|
+
:load_averages => load_averages(uptime_string),
|
111
|
+
:uptime => uptime(uptime_string),
|
112
|
+
:cpu => {
|
113
|
+
:arch => cpu_arch,
|
114
|
+
:type => cpu_type,
|
115
|
+
:vendor => cpu_vendor,
|
116
|
+
:speed => cpu_speed,
|
117
|
+
:count => cpu_count
|
118
|
+
}
|
119
|
+
}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module DCell
|
2
|
+
# A proxy object for a mailbox that delivers messages to the real mailbox on
|
3
|
+
# a remote node on a server far, far away...
|
4
|
+
class MailboxProxy
|
5
|
+
class InvalidNodeError < StandardError; end
|
6
|
+
|
7
|
+
def initialize(address)
|
8
|
+
mailbox_id, node_id = address.split("@")
|
9
|
+
|
10
|
+
# Create a proxy to the mailbox on the remote node
|
11
|
+
raise ArgumentError, "no mailbox_id given" unless mailbox_id
|
12
|
+
|
13
|
+
@node_id = node_id
|
14
|
+
@node = Node[node_id]
|
15
|
+
raise ArgumentError, "invalid node_id given" unless @node
|
16
|
+
|
17
|
+
@mailbox_id = mailbox_id
|
18
|
+
end
|
19
|
+
|
20
|
+
# name@host style address
|
21
|
+
def address
|
22
|
+
"#{@mailbox_id}@#{@node_id}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"#<DCell::MailboxProxy:0x#{object_id.to_s(16)} #{address}>"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Send a message to the mailbox
|
30
|
+
def <<(message)
|
31
|
+
@node.async.send_message Message::Relay.new(self, message)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Is the remote mailbox still alive?
|
35
|
+
def alive?
|
36
|
+
true # FIXME: hax!
|
37
|
+
end
|
38
|
+
|
39
|
+
# Custom marshaller for compatibility with Celluloid::Mailbox marshalling
|
40
|
+
def _dump(level)
|
41
|
+
"#{@mailbox_id}@#{@node_id}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Loader for custom marshal format
|
45
|
+
def self._load(address)
|
46
|
+
if mailbox = DCell::Router.find(address)
|
47
|
+
mailbox
|
48
|
+
else
|
49
|
+
DCell::MailboxProxy.new(address)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module DCell
|
2
|
+
class Message
|
3
|
+
attr_reader :id
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
# Memoize the original object ID so it will get marshalled
|
7
|
+
# Perhaps this should use a real UUID scheme
|
8
|
+
@id = object_id
|
9
|
+
end
|
10
|
+
|
11
|
+
# Heartbeat messages inform other nodes this node is healthy
|
12
|
+
class Heartbeat < Message
|
13
|
+
def initialize
|
14
|
+
@id = DCell.id
|
15
|
+
end
|
16
|
+
|
17
|
+
def dispatch
|
18
|
+
node = DCell::Node[@id]
|
19
|
+
node.handle_heartbeat if node
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Query a node for the address of an actor
|
24
|
+
class Find < Message
|
25
|
+
attr_reader :sender, :name
|
26
|
+
|
27
|
+
def initialize(sender, name)
|
28
|
+
super()
|
29
|
+
@sender, @name = sender, name
|
30
|
+
end
|
31
|
+
|
32
|
+
def dispatch
|
33
|
+
@sender << SuccessResponse.new(@id, Celluloid::Actor[@name])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# List all registered actors
|
38
|
+
class List < Message
|
39
|
+
attr_reader :sender
|
40
|
+
|
41
|
+
def initialize(sender)
|
42
|
+
super()
|
43
|
+
@sender = sender
|
44
|
+
end
|
45
|
+
|
46
|
+
def dispatch
|
47
|
+
@sender << SuccessResponse.new(@id, Celluloid::Actor.registered)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Relay a message to the given recipient
|
52
|
+
class Relay < Message
|
53
|
+
attr_reader :recipient, :message
|
54
|
+
|
55
|
+
def initialize(recipient, message)
|
56
|
+
super()
|
57
|
+
@recipient, @message = recipient, message
|
58
|
+
end
|
59
|
+
|
60
|
+
def dispatch
|
61
|
+
@recipient << @message
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/dcell/node.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
module DCell
|
2
|
+
# A node in a DCell cluster
|
3
|
+
class Node
|
4
|
+
include Celluloid
|
5
|
+
include Celluloid::FSM
|
6
|
+
attr_reader :id, :addr
|
7
|
+
|
8
|
+
finalizer :shutdown
|
9
|
+
|
10
|
+
NODE_DISCOVERY_TIMEOUT = 5
|
11
|
+
|
12
|
+
# FSM
|
13
|
+
default_state :disconnected
|
14
|
+
state :shutdown
|
15
|
+
state :disconnected, :to => [:connected, :shutdown]
|
16
|
+
state :connected do
|
17
|
+
send_heartbeat
|
18
|
+
Celluloid::Logger.info "Connected to #{id}"
|
19
|
+
end
|
20
|
+
state :partitioned do
|
21
|
+
@heartbeat.cancel
|
22
|
+
Celluloid::Logger.warn "Communication with #{id} interrupted"
|
23
|
+
end
|
24
|
+
|
25
|
+
@nodes = {}
|
26
|
+
@lock = Mutex.new
|
27
|
+
|
28
|
+
@heartbeat_rate = 5 # How often to send heartbeats in seconds
|
29
|
+
@heartbeat_timeout = 10 # How soon until a lost heartbeat triggers a node partition
|
30
|
+
|
31
|
+
# Singleton methods
|
32
|
+
class << self
|
33
|
+
include Enumerable
|
34
|
+
extend Forwardable
|
35
|
+
|
36
|
+
def_delegators "Celluloid::Actor[:node_manager]", :all, :each, :find, :[], :remove, :update
|
37
|
+
def_delegators "Celluloid::Actor[:node_manager]", :heartbeat_rate, :heartbeat_timeout
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(id, addr)
|
41
|
+
@id, @addr = id, addr
|
42
|
+
@socket = nil
|
43
|
+
@heartbeat = nil
|
44
|
+
|
45
|
+
# Total hax to accommodate the new Celluloid::FSM API
|
46
|
+
attach self
|
47
|
+
end
|
48
|
+
|
49
|
+
def update_client_address( addr )
|
50
|
+
@addr = addr
|
51
|
+
send_heartbeat
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_server_address(addr)
|
55
|
+
@addr = addr
|
56
|
+
end
|
57
|
+
|
58
|
+
def shutdown
|
59
|
+
transition :shutdown
|
60
|
+
@socket.close if @socket
|
61
|
+
end
|
62
|
+
|
63
|
+
# Obtain the node's 0MQ socket
|
64
|
+
def socket
|
65
|
+
return @socket if @socket
|
66
|
+
|
67
|
+
@socket = Celluloid::ZMQ::PushSocket.new
|
68
|
+
begin
|
69
|
+
@socket.connect addr
|
70
|
+
rescue IOError
|
71
|
+
@socket.close
|
72
|
+
@socket = nil
|
73
|
+
raise
|
74
|
+
end
|
75
|
+
|
76
|
+
transition :connected
|
77
|
+
@socket
|
78
|
+
end
|
79
|
+
|
80
|
+
# Find an actor registered with a given name on this node
|
81
|
+
def find(name)
|
82
|
+
request = Message::Find.new(Thread.mailbox, name)
|
83
|
+
send_message request
|
84
|
+
|
85
|
+
response = receive(NODE_DISCOVERY_TIMEOUT) do |msg|
|
86
|
+
msg.respond_to?(:request_id) && msg.request_id == request.id
|
87
|
+
end
|
88
|
+
|
89
|
+
return nil if response.nil?
|
90
|
+
abort response.value if response.is_a? ErrorResponse
|
91
|
+
response.value
|
92
|
+
end
|
93
|
+
alias_method :[], :find
|
94
|
+
|
95
|
+
# List all registered actors on this node
|
96
|
+
def actors
|
97
|
+
request = Message::List.new(Thread.mailbox)
|
98
|
+
send_message request
|
99
|
+
|
100
|
+
response = receive do |msg|
|
101
|
+
msg.respond_to?(:request_id) && msg.request_id == request.id
|
102
|
+
end
|
103
|
+
|
104
|
+
abort response.value if response.is_a? ErrorResponse
|
105
|
+
response.value
|
106
|
+
end
|
107
|
+
alias_method :all, :actors
|
108
|
+
|
109
|
+
# Send a message to another DCell node
|
110
|
+
def send_message(message)
|
111
|
+
begin
|
112
|
+
message = Marshal.dump(message)
|
113
|
+
rescue => ex
|
114
|
+
abort ex
|
115
|
+
end
|
116
|
+
|
117
|
+
socket << message
|
118
|
+
end
|
119
|
+
alias_method :<<, :send_message
|
120
|
+
|
121
|
+
# Send a heartbeat message after the given interval
|
122
|
+
def send_heartbeat
|
123
|
+
send_message DCell::Message::Heartbeat.new
|
124
|
+
@heartbeat = after(self.class.heartbeat_rate) { send_heartbeat }
|
125
|
+
end
|
126
|
+
|
127
|
+
# Handle an incoming heartbeat for this node
|
128
|
+
def handle_heartbeat
|
129
|
+
transition :connected
|
130
|
+
transition :partitioned, :delay => self.class.heartbeat_timeout
|
131
|
+
end
|
132
|
+
|
133
|
+
# Friendlier inspection
|
134
|
+
def inspect
|
135
|
+
"#<DCell::Node[#{@id}] @addr=#{@addr.inspect}>"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module DCell
|
2
|
+
# Manage nodes we're connected to
|
3
|
+
class NodeManager
|
4
|
+
include Celluloid::ZMQ
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
trap_exit :node_died
|
8
|
+
|
9
|
+
attr_reader :heartbeat_rate, :heartbeat_timeout
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@nodes = {}
|
13
|
+
|
14
|
+
@heartbeat_rate = 5 # How often to send heartbeats in seconds
|
15
|
+
@heartbeat_timeout = 10 # How soon until a lost heartbeat triggers a node partition
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return all available nodes in the cluster
|
19
|
+
def all
|
20
|
+
Directory.all.map do |node_id|
|
21
|
+
find node_id
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Iterate across all available nodes
|
26
|
+
def each
|
27
|
+
Directory.all.each do |node_id|
|
28
|
+
yield find node_id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Find a node by its node ID
|
33
|
+
def find(id)
|
34
|
+
node = @nodes[id]
|
35
|
+
return node if node
|
36
|
+
|
37
|
+
addr = Directory[id]
|
38
|
+
return unless addr
|
39
|
+
|
40
|
+
if id == DCell.id
|
41
|
+
node = DCell.me
|
42
|
+
else
|
43
|
+
node = Node.new(id, addr)
|
44
|
+
self.link node
|
45
|
+
end
|
46
|
+
|
47
|
+
@nodes[id] ||= node
|
48
|
+
@nodes[id]
|
49
|
+
end
|
50
|
+
alias_method :[], :find
|
51
|
+
|
52
|
+
def node_died(node, reason)
|
53
|
+
if reason.nil? # wtf?
|
54
|
+
# this wtf error seems to come from node socket writes
|
55
|
+
# when the socket is not reachable anymore
|
56
|
+
Celluloid::logger.debug "wtf?"
|
57
|
+
return
|
58
|
+
end
|
59
|
+
# Handle dead node???
|
60
|
+
end
|
61
|
+
|
62
|
+
def update(id)
|
63
|
+
addr = Directory[id]
|
64
|
+
return unless addr
|
65
|
+
if ( node = @nodes[id] ) and node.alive?
|
66
|
+
node.update_client_address( addr )
|
67
|
+
else
|
68
|
+
@nodes[id] = Node.new( id, addr )
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove(id)
|
73
|
+
if @nodes[id]
|
74
|
+
@nodes[id].terminate if @nodes[id].alive?
|
75
|
+
@nodes.delete(id)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|