stn-dcell 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +7 -0
  4. data/.rspec +4 -0
  5. data/.travis.yml +30 -0
  6. data/CHANGES.md +53 -0
  7. data/Gemfile +18 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +168 -0
  10. data/Rakefile +4 -0
  11. data/benchmarks/messaging.rb +73 -0
  12. data/benchmarks/receiver.rb +37 -0
  13. data/dcell.gemspec +29 -0
  14. data/examples/itchy.rb +26 -0
  15. data/examples/scratchy.rb +12 -0
  16. data/explorer/css/bootstrap-responsive.css +686 -0
  17. data/explorer/css/bootstrap-responsive.min.css +12 -0
  18. data/explorer/css/bootstrap.css +3990 -0
  19. data/explorer/css/bootstrap.min.css +689 -0
  20. data/explorer/css/explorer.css +28 -0
  21. data/explorer/ico/favicon.ico +0 -0
  22. data/explorer/img/glyphicons-halflings-white.png +0 -0
  23. data/explorer/img/glyphicons-halflings.png +0 -0
  24. data/explorer/img/logo.png +0 -0
  25. data/explorer/index.html.erb +94 -0
  26. data/explorer/js/bootstrap.js +1726 -0
  27. data/explorer/js/bootstrap.min.js +6 -0
  28. data/lib/dcell.rb +127 -0
  29. data/lib/dcell/actor_proxy.rb +30 -0
  30. data/lib/dcell/celluloid_ext.rb +120 -0
  31. data/lib/dcell/directory.rb +31 -0
  32. data/lib/dcell/explorer.rb +74 -0
  33. data/lib/dcell/future_proxy.rb +32 -0
  34. data/lib/dcell/global.rb +23 -0
  35. data/lib/dcell/info_service.rb +122 -0
  36. data/lib/dcell/mailbox_proxy.rb +53 -0
  37. data/lib/dcell/messages.rb +65 -0
  38. data/lib/dcell/node.rb +138 -0
  39. data/lib/dcell/node_manager.rb +79 -0
  40. data/lib/dcell/registries/cassandra_adapter.rb +126 -0
  41. data/lib/dcell/registries/mongodb_adapter.rb +85 -0
  42. data/lib/dcell/registries/redis_adapter.rb +95 -0
  43. data/lib/dcell/registries/zk_adapter.rb +123 -0
  44. data/lib/dcell/responses.rb +16 -0
  45. data/lib/dcell/router.rb +46 -0
  46. data/lib/dcell/rpc.rb +95 -0
  47. data/lib/dcell/rspec.rb +1 -0
  48. data/lib/dcell/server.rb +73 -0
  49. data/lib/dcell/version.rb +3 -0
  50. data/logo.png +0 -0
  51. data/spec/dcell/actor_proxy_spec.rb +72 -0
  52. data/spec/dcell/celluloid_ext_spec.rb +32 -0
  53. data/spec/dcell/directory_spec.rb +22 -0
  54. data/spec/dcell/explorer_spec.rb +17 -0
  55. data/spec/dcell/global_spec.rb +25 -0
  56. data/spec/dcell/node_spec.rb +23 -0
  57. data/spec/dcell/registries/mongodb_adapter_spec.rb +7 -0
  58. data/spec/dcell/registries/redis_adapter_spec.rb +6 -0
  59. data/spec/dcell/registries/zk_adapter_spec.rb +28 -0
  60. data/spec/options/01-options.rb +10 -0
  61. data/spec/options/02-registry.rb +13 -0
  62. data/spec/spec_helper.rb +28 -0
  63. data/spec/support/helpers.rb +46 -0
  64. data/spec/support/registry_examples.rb +35 -0
  65. data/spec/test_node.rb +38 -0
  66. data/tasks/cassandra.task +84 -0
  67. data/tasks/rspec.task +7 -0
  68. data/tasks/zookeeper.task +58 -0
  69. 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