dcell 0.0.0 → 0.0.1

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 (44) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +4 -0
  3. data/CHANGES.md +8 -0
  4. data/Gemfile +2 -0
  5. data/README.md +209 -0
  6. data/Rakefile +3 -0
  7. data/benchmarks/messaging.rb +67 -0
  8. data/benchmarks/receiver.rb +37 -0
  9. data/dcell.gemspec +10 -3
  10. data/lib/celluloid/README +8 -0
  11. data/lib/celluloid/zmq.rb +28 -0
  12. data/lib/celluloid/zmq/mailbox.rb +13 -0
  13. data/lib/celluloid/zmq/reactor.rb +74 -0
  14. data/lib/dcell.rb +92 -2
  15. data/lib/dcell/actor_proxy.rb +4 -0
  16. data/lib/dcell/application.rb +6 -0
  17. data/lib/dcell/celluloid_ext.rb +57 -0
  18. data/lib/dcell/directory.rb +23 -0
  19. data/lib/dcell/global.rb +23 -0
  20. data/lib/dcell/mailbox_proxy.rb +61 -0
  21. data/lib/dcell/messages.rb +67 -0
  22. data/lib/dcell/node.rb +120 -0
  23. data/lib/dcell/registries/redis_adapter.rb +86 -0
  24. data/lib/dcell/registries/zk_adapter.rb +122 -0
  25. data/lib/dcell/responses.rb +16 -0
  26. data/lib/dcell/router.rb +71 -0
  27. data/lib/dcell/rspec.rb +1 -0
  28. data/lib/dcell/server.rb +80 -0
  29. data/lib/dcell/version.rb +1 -1
  30. data/spec/celluloid/zmq/mailbox_spec.rb +6 -0
  31. data/spec/dcell/actor_proxy_spec.rb +60 -0
  32. data/spec/dcell/celluloid_ext_spec.rb +21 -0
  33. data/spec/dcell/directory_spec.rb +8 -0
  34. data/spec/dcell/global_spec.rb +21 -0
  35. data/spec/dcell/node_spec.rb +23 -0
  36. data/spec/dcell/registries/redis_adapter_spec.rb +6 -0
  37. data/spec/dcell/registries/zk_adapter_spec.rb +11 -0
  38. data/spec/spec_helper.rb +16 -0
  39. data/spec/support/helpers.rb +40 -0
  40. data/spec/support/registry_examples.rb +35 -0
  41. data/spec/test_node.rb +33 -0
  42. data/tasks/rspec.task +7 -0
  43. data/tasks/zookeeper.task +58 -0
  44. metadata +111 -7
data/lib/dcell.rb CHANGED
@@ -1,5 +1,95 @@
1
- require "dcell/version"
1
+ require 'celluloid'
2
+ require 'celluloid/zmq'
3
+ require 'dcell/version'
2
4
 
5
+ require 'dcell/actor_proxy'
6
+ require 'dcell/directory'
7
+ require 'dcell/mailbox_proxy'
8
+ require 'dcell/messages'
9
+ require 'dcell/node'
10
+ require 'dcell/global'
11
+ require 'dcell/responses'
12
+ require 'dcell/router'
13
+ require 'dcell/server'
14
+
15
+ require 'dcell/registries/redis_adapter'
16
+ require 'dcell/application'
17
+ require 'dcell/celluloid_ext'
18
+
19
+ # Distributed Celluloid
3
20
  module DCell
4
- # Your code goes here...
21
+ DEFAULT_PORT = 7777 # Default DCell port
22
+ ZMQ_POOL_SIZE = 1 # DCell uses a fixed-size 0MQ thread pool
23
+ @zmq_context = ::ZMQ::Context.new(ZMQ_POOL_SIZE)
24
+ @config_lock = Mutex.new
25
+
26
+ class << self
27
+ attr_reader :me, :registry, :zmq_context
28
+
29
+ # Configure DCell with the following options:
30
+ #
31
+ # * id: to identify the local node, defaults to hostname
32
+ # * addr: 0MQ address of the local node (e.g. tcp://4.3.2.1:7777)
33
+ # *
34
+ def setup(options = {})
35
+ # Stringify keys :/
36
+ options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
37
+
38
+ @config_lock.synchronize do
39
+ @configuration = {
40
+ 'id' => generate_node_id,
41
+ 'addr' => "tcp://127.0.0.1:#{DEFAULT_PORT}",
42
+ 'registry' => {'adapter' => 'redis', 'server' => 'localhost'}
43
+ }.merge(options)
44
+
45
+ @me = Node.new @configuration['id'], @configuration['addr']
46
+
47
+ registry_adapter = @configuration['registry'][:adapter] || @configuration['registry']['adapter']
48
+ raise ArgumentError, "no registry adapter given in config" unless registry_adapter
49
+
50
+ registry_class_name = registry_adapter.split("_").map(&:capitalize).join << "Adapter"
51
+
52
+ begin
53
+ registry_class = DCell::Registry.const_get registry_class_name
54
+ rescue NameError
55
+ raise ArgumentError, "invalid registry adapter: #{@configuration['registry']['adapter']}"
56
+ end
57
+
58
+ @registry = registry_class.new(@configuration['registry'])
59
+
60
+ addr = @configuration['public'] || @configuration['addr']
61
+ DCell::Directory.set @configuration['id'], addr
62
+ end
63
+
64
+ me
65
+ end
66
+
67
+ # Obtain the local node ID
68
+ def id; @configuration['id']; end
69
+
70
+ # Obtain the 0MQ address to the local mailbox
71
+ def addr; @configuration['addr']; end
72
+ alias_method :address, :addr
73
+
74
+ # Attempt to generate a unique node ID for this machine
75
+ def generate_node_id
76
+ `hostname`.strip # Super creative I know
77
+ end
78
+
79
+ # Run the DCell application
80
+ def run
81
+ DCell::Application.run
82
+ end
83
+
84
+ # Run the DCell application in the background
85
+ def run!
86
+ DCell::Application.run!
87
+ end
88
+
89
+ # Start combines setup and run! into a single step
90
+ def start(options = {})
91
+ setup options
92
+ run!
93
+ end
94
+ end
5
95
  end
@@ -0,0 +1,4 @@
1
+ module DCell
2
+ # Proxy object for actors that live on remote nodes
3
+ class ActorProxy < Celluloid::ActorProxy; end
4
+ end
@@ -0,0 +1,6 @@
1
+ module DCell
2
+ # Actors which run when DCell is active
3
+ class Application < Celluloid::Application
4
+ supervise Server, :as => :dcell_server
5
+ end
6
+ end
@@ -0,0 +1,57 @@
1
+ # Celluloid mailboxes are the universal message exchange points. You won't
2
+ # be able to marshal them though, unfortunately, because they contain
3
+ # mutexes.
4
+ #
5
+ # DCell provides a message routing layer between nodes that can direct
6
+ # messages back to local mailboxes. To accomplish this, DCell adds custom
7
+ # marshalling to mailboxes so that if they're unserialized on a remote
8
+ # node you instead get a proxy object that routes messages through the
9
+ # DCell overlay network back to the node where the actor actually exists
10
+
11
+ module Celluloid
12
+ class ActorProxy
13
+ # Marshal uses respond_to? to determine if this object supports _dump so
14
+ # unfortunately we have to monkeypatch in _dump support as the proxy
15
+ # itself normally jacks respond_to? and proxies to the actor
16
+ alias_method :__respond_to?, :respond_to?
17
+ def respond_to?(meth)
18
+ return false if meth == :marshal_dump
19
+ return true if meth == :_dump
20
+ __respond_to? meth
21
+ end
22
+
23
+ # Dump an actor proxy via its mailbox
24
+ def _dump(level)
25
+ @mailbox._dump(level)
26
+ end
27
+
28
+ # Create an actor proxy object which routes messages over DCell's overlay
29
+ # network and back to the original mailbox
30
+ def self._load(string)
31
+ mailbox = Celluloid::Mailbox._load(string)
32
+
33
+ case mailbox
34
+ when DCell::MailboxProxy
35
+ DCell::ActorProxy.new mailbox
36
+ when Celluloid::Mailbox
37
+ Celluloid::ActorProxy.new(mailbox)
38
+ else raise "funny, I did not expect to see a #{mailbox.class} here"
39
+ end
40
+ end
41
+ end
42
+
43
+ class Mailbox
44
+ # This custom dumper registers actors with the DCell registry so they can
45
+ # be reached remotely.
46
+ def _dump(level)
47
+ mailbox_id = DCell::Router.register self
48
+ "#{mailbox_id}@#{DCell.id}"
49
+ end
50
+
51
+ # Create a mailbox proxy object which routes messages over DCell's overlay
52
+ # network and back to the original mailbox
53
+ def self._load(string)
54
+ DCell::MailboxProxy._load(string)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,23 @@
1
+ module DCell
2
+ # Directory of nodes connected to the DCell cluster
3
+ module Directory
4
+ extend self
5
+
6
+ # Get the URL for a particular Node ID
7
+ def get(node_id)
8
+ DCell.registry.get_node node_id
9
+ end
10
+ alias_method :[], :get
11
+
12
+ # Set the address of a particular Node ID
13
+ def set(node_id, addr)
14
+ DCell.registry.set_node node_id, addr
15
+ end
16
+ alias_method :[]=, :set
17
+
18
+ # List all of the node IDs in the directory
19
+ def all
20
+ DCell.registry.nodes
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module DCell
2
+ # Global object registry shared among all DCell nodes
3
+ module Global
4
+ extend self
5
+
6
+ # Get a global value
7
+ def get(key)
8
+ DCell.registry.get_global key.to_s
9
+ end
10
+ alias_method :[], :get
11
+
12
+ # Set a global value
13
+ def set(key, value)
14
+ DCell.registry.set_global key.to_s, value
15
+ end
16
+ alias_method :[]=, :set
17
+
18
+ # Get the keys for all the globals in the system
19
+ def keys
20
+ DCell.registry.global_keys.map(&:to_sym)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,61 @@
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(node_id, mailbox_id)
8
+ raise ArgumentError, "no mailbox_id given" unless mailbox_id
9
+
10
+ @node_id = node_id
11
+ @node = Node[node_id]
12
+ raise ArgumentError, "invalid node_id given" unless @node
13
+
14
+ @mailbox_id = mailbox_id
15
+ end
16
+
17
+ # name@host style address
18
+ def address
19
+ "#{@mailbox_id}@#{@node_id}"
20
+ end
21
+
22
+ def inspect
23
+ "#<DCell::MailboxProxy:0x#{object_id.to_s(16)} #{address}>"
24
+ end
25
+
26
+ # Send a message to the mailbox
27
+ def <<(message)
28
+ @node.send_message! Message::Relay.new(@mailbox_id, message)
29
+ end
30
+
31
+ # Send a system event
32
+ def system_event(event)
33
+ @node.send_message! Message::SystemEvent.new(@mailbox_id, event)
34
+ end
35
+
36
+ # Is the remote mailbox still alive?
37
+ def alive?
38
+ true # FIXME: hax!
39
+ end
40
+
41
+ # Custom marshaller for compatibility with Celluloid::Mailbox marshalling
42
+ def _dump(level)
43
+ "#{@mailbox_id}@#{@node_id}"
44
+ end
45
+
46
+ # Loader for custom marshal format
47
+ def self._load(string)
48
+ mailbox_id, node_id = string.split("@")
49
+
50
+ if DCell.id == node_id
51
+ # If we're on the local node, find the real mailbox
52
+ mailbox = DCell::Router.find mailbox_id
53
+ raise "tried to unmarshal dead Celluloid::Mailbox: #{mailbox_id}" unless mailbox
54
+ mailbox
55
+ else
56
+ # Create a proxy to the mailbox on the remote node
57
+ DCell::MailboxProxy.new(node_id, mailbox_id)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,67 @@
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
+ # Query a node for the address of an actor
12
+ class Find < Message
13
+ attr_reader :caller, :name
14
+
15
+ def initialize(caller, name)
16
+ super()
17
+ @caller, @name = caller, name
18
+ end
19
+
20
+ def dispatch
21
+ @caller << SuccessResponse.new(@id, Celluloid::Actor[@name])
22
+ end
23
+ end
24
+
25
+ # List all registered actors
26
+ class List < Message
27
+ attr_reader :caller
28
+
29
+ def initialize(caller)
30
+ super()
31
+ @caller = caller
32
+ end
33
+
34
+ def dispatch
35
+ @caller << SuccessResponse.new(@id, Celluloid::Actor.registered)
36
+ end
37
+ end
38
+
39
+ # Relay a message to the given recipient
40
+ class Relay < Message
41
+ attr_reader :recipient, :message
42
+
43
+ def initialize(recipient, message)
44
+ super()
45
+ @recipient, @message = recipient, message
46
+ end
47
+
48
+ def dispatch
49
+ DCell::Router.route @recipient, @message
50
+ end
51
+ end
52
+
53
+ # Send a system event to the given recipient
54
+ class SystemEvent < Message
55
+ attr_reader :recipient, :event
56
+
57
+ def initialize(recipient, event)
58
+ super()
59
+ @recipient, @event = recipient, event
60
+ end
61
+
62
+ def dispatch
63
+ DCell::Router.route_system_event @recipient, @event
64
+ end
65
+ end
66
+ end
67
+ end
data/lib/dcell/node.rb ADDED
@@ -0,0 +1,120 @@
1
+ module DCell
2
+ # A node in a DCell cluster
3
+ class Node
4
+ include Celluloid
5
+ attr_reader :id, :addr
6
+
7
+ @nodes = {}
8
+ @lock = Mutex.new
9
+
10
+ class << self
11
+ include Enumerable
12
+
13
+ # Return all available nodes in the cluster
14
+ def all
15
+ Directory.all.map do |node_id|
16
+ find node_id
17
+ end
18
+ end
19
+
20
+ # Iterate across all available nodes
21
+ def each
22
+ Directory.all.each do |node_id|
23
+ yield find node_id
24
+ end
25
+ end
26
+
27
+ # Find a node by its node ID
28
+ def find(id)
29
+ node = @lock.synchronize { @nodes[id] }
30
+ return node if node
31
+
32
+ addr = Directory[id]
33
+
34
+ if addr
35
+ if id == DCell.id
36
+ node = DCell.me
37
+ else
38
+ node = Node.new(id, addr)
39
+ end
40
+
41
+ @lock.synchronize do
42
+ @nodes[id] ||= node
43
+ @nodes[id]
44
+ end
45
+ end
46
+ end
47
+ alias_method :[], :find
48
+ end
49
+
50
+ def initialize(id, addr)
51
+ @id, @addr = id, addr
52
+ @socket = nil
53
+ end
54
+
55
+ def finalize
56
+ @socket.close if socket
57
+ end
58
+
59
+ # Obtain the node's 0MQ socket
60
+ def socket
61
+ return @socket if @socket
62
+
63
+ @socket = DCell.zmq_context.socket(::ZMQ::PUSH)
64
+ unless ::ZMQ::Util.resultcode_ok? @socket.connect @addr
65
+ @socket.close
66
+ @socket = nil
67
+ raise "error connecting to #{addr}: #{::ZMQ::Util.error_string}"
68
+ end
69
+
70
+ @socket
71
+ end
72
+
73
+ # Find an actor registered with a given name on this node
74
+ def find(name)
75
+ request = Message::Find.new(Thread.mailbox, name)
76
+ send_message request
77
+
78
+ response = receive do |msg|
79
+ msg.respond_to?(:request_id) && msg.request_id == request.id
80
+ end
81
+
82
+ abort response.value if response.is_a? ErrorResponse
83
+ response.value
84
+ end
85
+ alias_method :[], :find
86
+
87
+ # List all registered actors on this node
88
+ def actors
89
+ request = Message::List.new(Thread.mailbox)
90
+ send_message request
91
+
92
+ response = receive do |msg|
93
+ msg.respond_to?(:request_id) && msg.request_id == request.id
94
+ end
95
+
96
+ abort response.value if response.is_a? ErrorResponse
97
+ response.value
98
+ end
99
+ alias_method :all, :actors
100
+
101
+ # Send a message to another DCell node
102
+ def send_message(message)
103
+ begin
104
+ string = Marshal.dump(message)
105
+ rescue => ex
106
+ abort ex
107
+ end
108
+
109
+ unless ::ZMQ::Util.resultcode_ok? socket.send_string string
110
+ raise "error sending 0MQ message: #{::ZMQ::Util.error_string}"
111
+ end
112
+ end
113
+ alias_method :<<, :send_message
114
+
115
+ # Friendlier inspection
116
+ def inspect
117
+ "#<DCell::Node[#{@id}] @addr=#{@addr.inspect}>"
118
+ end
119
+ end
120
+ end