stn-dcell 0.16.0

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.
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