dcell 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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