dcell 0.10.0 → 0.12.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,33 +3,39 @@ module DCell
3
3
  class Node
4
4
  include Celluloid
5
5
  include Celluloid::FSM
6
- attr_reader :id, :addr, :timestamp
6
+ attr_reader :id, :addr
7
7
 
8
8
  # FSM
9
9
  default_state :disconnected
10
10
  state :shutdown
11
11
  state :disconnected, :to => [:connected, :shutdown]
12
12
  state :connected do
13
+ send_heartbeat
13
14
  Celluloid::Logger.info "Connected to #{id}"
14
15
  end
15
16
  state :partitioned do
16
17
  Celluloid::Logger.warn "Communication with #{id} interrupted"
17
18
  end
19
+
20
+ @nodes = {}
21
+ @lock = Mutex.new
22
+
23
+ @heartbeat_rate = 5 # How often to send heartbeats in seconds
24
+ @heartbeat_timeout = 10 # How soon until a lost heartbeat triggers a node partition
18
25
 
19
26
  # Singleton methods
20
27
  class << self
21
28
  include Enumerable
22
29
  extend Forwardable
23
30
 
24
- def_delegators "Celluloid::Actor[:node_manager]", :all, :each, :find, :[], :handle_gossip
25
- def_delegators "Celluloid::Actor[:node_manager]", :gossip_rate, :heartbeat_timeout
31
+ def_delegators "Celluloid::Actor[:node_manager]", :all, :each, :find, :[]
32
+ def_delegators "Celluloid::Actor[:node_manager]", :heartbeat_rate, :heartbeat_timeout
26
33
  end
27
34
 
28
35
  def initialize(id, addr)
29
36
  @id, @addr = id, addr
30
- @timestamp = 0
31
37
  @socket = nil
32
- @fresh = true
38
+ @heartbeat = nil
33
39
 
34
40
  # Total hax to accommodate the new Celluloid::FSM API
35
41
  attach self
@@ -37,7 +43,6 @@ module DCell
37
43
 
38
44
  def finalize
39
45
  transition :shutdown
40
- @gossip.cancel if @gossip
41
46
  @socket.close if @socket
42
47
  end
43
48
 
@@ -97,26 +102,17 @@ module DCell
97
102
  socket << message
98
103
  end
99
104
  alias_method :<<, :send_message
100
-
101
- def tick
102
- @timestamp += 1
103
- end
104
-
105
- def fresh?
106
- @fresh
105
+
106
+ # Send a heartbeat message after the given interval
107
+ def send_heartbeat
108
+ send_message DCell::Message::Heartbeat.new
109
+ @heartbeat = after(self.class.heartbeat_rate) { send_heartbeat }
107
110
  end
108
111
 
109
- # Handle an incoming timestamp observation for this node
110
- def handle_timestamp(t)
111
- @fresh = false if t > 0
112
- if @timestamp < t
113
- @timestamp = t
114
- transition :connected
115
- transition :partitioned, :delay => self.class.heartbeat_timeout
116
- unless state == :connected
117
- Celluloid::Logger.info "Revived node #{id}"
118
- end
119
- end
112
+ # Handle an incoming heartbeat for this node
113
+ def handle_heartbeat
114
+ transition :connected
115
+ transition :partitioned, :delay => self.class.heartbeat_timeout
120
116
  end
121
117
 
122
118
  # Friendlier inspection
@@ -4,15 +4,13 @@ module DCell
4
4
  include Celluloid::ZMQ
5
5
  include Enumerable
6
6
 
7
- attr_reader :gossip_rate, :heartbeat_timeout
7
+ attr_reader :heartbeat_rate, :heartbeat_timeout
8
8
 
9
9
  def initialize
10
10
  @nodes = {}
11
11
 
12
- @gossip_rate = 5 # How often to send gossip in seconds
12
+ @heartbeat_rate = 5 # How often to send heartbeats in seconds
13
13
  @heartbeat_timeout = 10 # How soon until a lost heartbeat triggers a node partition
14
- each { |node| node.socket if node } # Connect all so we can gossip
15
- @gossip = after(gossip_rate) { gossip_timeout }
16
14
  end
17
15
 
18
16
  # Return all available nodes in the cluster
@@ -47,35 +45,5 @@ module DCell
47
45
  @nodes[id]
48
46
  end
49
47
  alias_method :[], :find
50
-
51
- # Send gossip to a random node (except ourself) after the given interval
52
- def gossip_timeout
53
- nodes = select { |node| node.state == :connected }
54
- peer = nodes.select { |node| node.id != DCell.id }.sample(1)[0]
55
- if peer
56
- nodes = nodes.inject([]) { |a,n| a << [n.id, n.addr, n.timestamp]; a }
57
- data = nil
58
- if DCell.registry.is_a? Registry::GossipAdapter
59
- data = peer.fresh? ? DCell.registry.values : DCell.registry.changed
60
- end
61
- DCell.me.tick
62
- peer.send_message DCell::Message::Gossip.new nodes, data
63
- end
64
- @gossip = after(gossip_rate) { gossip_timeout }
65
- end
66
-
67
- def handle_gossip(peers, data)
68
- peers.each do |id, addr, timestamp|
69
- if (node = find(id))
70
- node.handle_timestamp! timestamp
71
- else
72
- Directory[id] = addr
73
- Celluloid::Logger.info "Found node #{id}"
74
- end
75
- end
76
- if DCell.registry.is_a? Registry::GossipAdapter
77
- data.map { |data| DCell.registry.observe data } if data
78
- end
79
- end
80
48
  end
81
49
  end
@@ -44,13 +44,45 @@ module DCell
44
44
 
45
45
  cass = Cassandra.new(keyspace, options[:servers])
46
46
 
47
+ @node_registry = NodeRegistry.new(cass, columnfamily)
47
48
  @global_registry = GlobalRegistry.new(cass, columnfamily)
48
49
  end
49
50
 
51
+ def clear_nodes
52
+ @node_registry.clear
53
+ end
54
+
50
55
  def clear_globals
51
56
  @global_registry.clear
52
57
  end
53
58
 
59
+ class NodeRegistry
60
+ def initialize(cass, cf)
61
+ @cass = cass
62
+ @cf = cf
63
+ end
64
+
65
+ def get(node_id)
66
+ @cass.get @cf, "nodes", node_id
67
+ end
68
+
69
+ def set(node_id, addr)
70
+ @cass.insert @cf, "nodes", { node_id => addr }
71
+ end
72
+
73
+ def nodes
74
+ @cass.get(@cf, "nodes").keys
75
+ end
76
+
77
+ def clear
78
+ @cass.del @cf, "nodes"
79
+ end
80
+ end
81
+
82
+ def get_node(node_id); @node_registry.get(node_id) end
83
+ def set_node(node_id, addr); @node_registry.set(node_id, addr) end
84
+ def nodes; @node_registry.nodes end
85
+
54
86
  class GlobalRegistry
55
87
  def initialize(cass, cf)
56
88
  @cass = cass
@@ -16,6 +16,7 @@ module DCell
16
16
  # @moneta = Moneta::TieredCache.new options
17
17
  @moneta = Moneta::Memory.new options
18
18
 
19
+ @node_registry = Registry.new(@moneta, :nodes)
19
20
  @global_registry = Registry.new(@moneta, :globals)
20
21
  end
21
22
 
@@ -38,6 +39,7 @@ module DCell
38
39
  end
39
40
 
40
41
  # DCell registry behaviors
42
+ alias_method :nodes, :all
41
43
  alias_method :global_keys, :all
42
44
 
43
45
  def clear
@@ -45,6 +47,11 @@ module DCell
45
47
  end
46
48
  end
47
49
 
50
+ def get_node(node_id); @node_registry.get(node_id) end
51
+ def set_node(node_id, addr); @node_registry.set(node_id, addr) end
52
+ def nodes; @node_registry.nodes end
53
+ def clear_nodes; @node_registry.clear end
54
+
48
55
  def get_global(key); @global_registry.get(key) end
49
56
  def set_global(key, value); @global_registry.set(key, value) end
50
57
  def global_keys; @global_registry.global_keys end
@@ -14,13 +14,44 @@ module DCell
14
14
  redis = Redis.new options
15
15
  @redis = Redis::Namespace.new @namespace, :redis => redis
16
16
 
17
+ @node_registry = NodeRegistry.new(@redis)
17
18
  @global_registry = GlobalRegistry.new(@redis)
18
19
  end
19
20
 
21
+ def clear_nodes
22
+ @node_registry.clear
23
+ end
24
+
20
25
  def clear_globals
21
26
  @global_registry.clear
22
27
  end
23
28
 
29
+ class NodeRegistry
30
+ def initialize(redis)
31
+ @redis = redis
32
+ end
33
+
34
+ def get(node_id)
35
+ @redis.hget 'nodes', node_id
36
+ end
37
+
38
+ def set(node_id, addr)
39
+ @redis.hset 'nodes', node_id, addr
40
+ end
41
+
42
+ def nodes
43
+ @redis.hkeys 'nodes'
44
+ end
45
+
46
+ def clear
47
+ @redis.del 'nodes'
48
+ end
49
+ end
50
+
51
+ def get_node(node_id); @node_registry.get(node_id) end
52
+ def set_node(node_id, addr); @node_registry.set(node_id, addr) end
53
+ def nodes; @node_registry.nodes end
54
+
24
55
  class GlobalRegistry
25
56
  def initialize(redis)
26
57
  @redis = redis
@@ -36,13 +36,51 @@ module DCell
36
36
  end
37
37
 
38
38
  @zk = ZK.new(*servers)
39
+ @node_registry = NodeRegistry.new(@zk, @base_path)
39
40
  @global_registry = GlobalRegistry.new(@zk, @base_path)
40
41
  end
41
42
 
43
+ def clear_nodes
44
+ @node_registry.clear
45
+ end
46
+
42
47
  def clear_globals
43
48
  @global_registry.clear
44
49
  end
45
50
 
51
+ class NodeRegistry
52
+ def initialize(zk, base_path)
53
+ @zk, @base_path = zk, "#{base_path}/nodes"
54
+ @zk.mkdir_p @base_path
55
+ end
56
+
57
+ def get(node_id)
58
+ result, _ = @zk.get("#{@base_path}/#{node_id}")
59
+ result
60
+ rescue ZK::Exceptions::NoNode
61
+ end
62
+
63
+ def set(node_id, addr)
64
+ path = "#{@base_path}/#{node_id}"
65
+ @zk.set path, addr
66
+ rescue ZK::Exceptions::NoNode
67
+ @zk.create path, addr
68
+ end
69
+
70
+ def nodes
71
+ @zk.children @base_path
72
+ end
73
+
74
+ def clear
75
+ @zk.rm_rf @base_path
76
+ @zk.mkdir_p @base_path
77
+ end
78
+ end
79
+
80
+ def get_node(node_id); @node_registry.get(node_id) end
81
+ def set_node(node_id, addr); @node_registry.set(node_id, addr) end
82
+ def nodes; @node_registry.nodes end
83
+
46
84
  class GlobalRegistry
47
85
  def initialize(zk, base_path)
48
86
  @zk, @base_path = zk, "#{base_path}/globals"
@@ -81,4 +119,4 @@ module DCell
81
119
  def global_keys; @global_registry.global_keys end
82
120
  end
83
121
  end
84
- end
122
+ end
@@ -43,28 +43,6 @@ module DCell
43
43
  end
44
44
  end
45
45
 
46
- # Route a message to a given mailbox ID
47
- def route(recipient, message)
48
- recipient = find(recipient) if recipient.is_a? String
49
-
50
- if recipient
51
- recipient << message
52
- else
53
- Celluloid::Logger.debug("received message for invalid actor: #{mailbox_address.inspect}")
54
- end
55
- end
56
-
57
- # Route a system event to a given mailbox ID
58
- def route_system_event(mailbox_address, event)
59
- recipient = find mailbox_address
60
-
61
- if recipient
62
- recipient.system_event event
63
- else
64
- Celluloid::Logger.debug("received message for invalid actor: #{mailbox_address.inspect}")
65
- end
66
- end
67
-
68
46
  # Prune all entries that point to dead objects
69
47
  def gc
70
48
  @mutex.synchronize do
@@ -6,7 +6,7 @@ module DCell
6
6
  # Bind to the given 0MQ address (in URL form ala tcp://host:port)
7
7
  def initialize
8
8
  # The gossip protocol is dependent on the node manager
9
- link Actor[:node_manager]
9
+ link Celluloid::Actor[:node_manager]
10
10
 
11
11
  @addr = DCell.addr
12
12
  @socket = PullSocket.new
@@ -1,3 +1,3 @@
1
1
  module DCell
2
- VERSION = "0.10.0"
2
+ VERSION = "0.12.0.pre"
3
3
  end
@@ -5,4 +5,4 @@ describe DCell::Directory do
5
5
  DCell::Directory["foobar"] = "tcp://localhost:1870"
6
6
  DCell::Directory["foobar"].should == "tcp://localhost:1870"
7
7
  end
8
- end
8
+ end
@@ -11,10 +11,6 @@ describe DCell::Global do
11
11
 
12
12
  # Double check the global value is available on all nodes
13
13
  node = DCell::Node['test_node']
14
- 20.downto(0) do |i|
15
- break if node[:test_actor].the_answer
16
- sleep 1
17
- end
18
14
  node[:test_actor].the_answer.should == 42
19
15
  end
20
16
 
@@ -7,8 +7,8 @@ Dir['./spec/support/*.rb'].map { |f| require f }
7
7
 
8
8
  RSpec.configure do |config|
9
9
  config.before(:suite) do
10
- DCell.setup :directory => { :id => 'test_node', :addr => "tcp://127.0.0.1:#{TestNode::PORT}" }
11
- @supervisor = DCell.run!
10
+ DCell.setup
11
+ DCell.run!
12
12
 
13
13
  TestNode.start
14
14
  TestNode.wait_until_ready
@@ -17,7 +17,4 @@ RSpec.configure do |config|
17
17
  config.after(:suite) do
18
18
  TestNode.stop
19
19
  end
20
- end
21
-
22
- # FIXME: this is hax to bypass the other at_exit handlers
23
- at_exit { exit! $!.status }
20
+ end
@@ -1,4 +1,22 @@
1
1
  shared_context "a DCell registry" do
2
+ context "node registry" do
3
+ before :each do
4
+ subject.clear_nodes
5
+ end
6
+
7
+ it "stores node addresses" do
8
+ address = "tcp://localhost:7777"
9
+
10
+ subject.set_node("foobar", address)
11
+ subject.get_node("foobar").should == address
12
+ end
13
+
14
+ it "stores the IDs of all nodes" do
15
+ subject.set_node("foobar", "tcp://localhost:7777")
16
+ subject.nodes.should include "foobar"
17
+ end
18
+ end
19
+
2
20
  context "global registry" do
3
21
  before :each do
4
22
  subject.clear_globals
@@ -6,7 +6,7 @@ require 'bundler'
6
6
  Bundler.setup
7
7
 
8
8
  require 'dcell'
9
- DCell.setup :id => 'test_node', :addr => 'tcp://127.0.0.1:21264'
9
+ DCell.start :id => 'test_node', :addr => 'tcp://127.0.0.1:21264'
10
10
 
11
11
  class TestActor
12
12
  include Celluloid
@@ -25,9 +25,5 @@ class TestActor
25
25
  end
26
26
  end
27
27
 
28
- class TestGroup < Celluloid::Group
29
- supervise DCell::Group
30
- supervise TestActor, :as => :test_actor
31
- end
32
-
33
- TestGroup.run
28
+ TestActor.supervise_as :test_actor
29
+ sleep