dcell 0.10.0 → 0.12.0.pre

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