dcell 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.travis.yml +4 -1
  2. data/CHANGES.md +11 -0
  3. data/Gemfile +6 -3
  4. data/LICENSE.txt +1 -1
  5. data/README.md +27 -243
  6. data/benchmarks/receiver.rb +2 -2
  7. data/dcell.gemspec +7 -7
  8. data/explorer/css/bootstrap-responsive.css +686 -0
  9. data/explorer/css/bootstrap-responsive.min.css +12 -0
  10. data/explorer/css/bootstrap.css +3990 -0
  11. data/explorer/css/bootstrap.min.css +689 -0
  12. data/explorer/css/explorer.css +28 -0
  13. data/explorer/ico/favicon.ico +0 -0
  14. data/explorer/img/glyphicons-halflings-white.png +0 -0
  15. data/explorer/img/glyphicons-halflings.png +0 -0
  16. data/explorer/img/logo.png +0 -0
  17. data/explorer/index.html.erb +94 -0
  18. data/explorer/js/bootstrap.js +1726 -0
  19. data/explorer/js/bootstrap.min.js +6 -0
  20. data/lib/dcell.rb +27 -2
  21. data/lib/dcell/celluloid_ext.rb +14 -3
  22. data/lib/dcell/directory.rb +15 -3
  23. data/lib/dcell/explorer.rb +76 -0
  24. data/lib/dcell/future_proxy.rb +32 -0
  25. data/lib/dcell/info_service.rb +117 -0
  26. data/lib/dcell/mailbox_proxy.rb +6 -7
  27. data/lib/dcell/messages.rb +5 -6
  28. data/lib/dcell/node.rb +25 -55
  29. data/lib/dcell/node_manager.rb +81 -0
  30. data/lib/dcell/registries/cassandra_adapter.rb +86 -0
  31. data/lib/dcell/registries/gossip/core.rb +235 -0
  32. data/lib/dcell/registries/gossip_adapter.rb +26 -0
  33. data/lib/dcell/registries/moneta_adapter.rb +0 -7
  34. data/lib/dcell/registries/redis_adapter.rb +0 -31
  35. data/lib/dcell/registries/zk_adapter.rb +1 -39
  36. data/lib/dcell/router.rb +37 -30
  37. data/lib/dcell/rpc.rb +23 -23
  38. data/lib/dcell/server.rb +5 -2
  39. data/lib/dcell/version.rb +1 -1
  40. data/logo.png +0 -0
  41. data/spec/dcell/actor_proxy_spec.rb +4 -0
  42. data/spec/dcell/celluloid_ext_spec.rb +11 -0
  43. data/spec/dcell/directory_spec.rb +1 -1
  44. data/spec/dcell/explorer_spec.rb +17 -0
  45. data/spec/dcell/global_spec.rb +4 -0
  46. data/spec/dcell/registries/gossip_adapter_spec.rb +6 -0
  47. data/spec/spec_helper.rb +14 -7
  48. data/spec/support/registry_examples.rb +0 -18
  49. data/tasks/cassandra.task +84 -0
  50. metadata +55 -35
  51. data/celluloid-zmq/.gitignore +0 -17
  52. data/celluloid-zmq/.rspec +0 -4
  53. data/celluloid-zmq/CHANGES.md +0 -31
  54. data/celluloid-zmq/Gemfile +0 -7
  55. data/celluloid-zmq/README.md +0 -56
  56. data/celluloid-zmq/Rakefile +0 -7
  57. data/celluloid-zmq/celluloid-zmq.gemspec +0 -28
  58. data/celluloid-zmq/lib/celluloid/zmq.rb +0 -36
  59. data/celluloid-zmq/lib/celluloid/zmq/reactor.rb +0 -90
  60. data/celluloid-zmq/lib/celluloid/zmq/sockets.rb +0 -130
  61. data/celluloid-zmq/lib/celluloid/zmq/version.rb +0 -5
  62. data/celluloid-zmq/lib/celluloid/zmq/waker.rb +0 -55
  63. data/celluloid-zmq/spec/celluloid/zmq/actor_spec.rb +0 -6
  64. data/celluloid-zmq/spec/spec_helper.rb +0 -2
@@ -0,0 +1,81 @@
1
+ module DCell
2
+ # Manage nodes we're connected to
3
+ class NodeManager
4
+ include Celluloid::ZMQ
5
+ include Enumerable
6
+
7
+ attr_reader :gossip_rate, :heartbeat_timeout
8
+
9
+ def initialize
10
+ @nodes = {}
11
+
12
+ @gossip_rate = 5 # How often to send gossip in seconds
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
+ 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
+ end
45
+
46
+ @nodes[id] ||= node
47
+ @nodes[id]
48
+ end
49
+ 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
+ end
81
+ end
@@ -0,0 +1,86 @@
1
+ require 'cassandra'
2
+
3
+ # create the keyspace / columnfamily with cqlsh
4
+ #
5
+ # create keyspace dcell
6
+ # with strategy_class='SimpleStrategy'
7
+ # and strategy_options:replication_factor=3;
8
+ #
9
+ # create columnfamily dcell (dcell_type ascii primary key);
10
+
11
+ # not sure this is right yet ...
12
+ # Keyspace "whatever" [
13
+ # ColumnFamily "dcell" {
14
+ # RowKey "nodes": {
15
+ # <nodeid>: <address>,
16
+ # <nodeid>: <address>,
17
+ # ...
18
+ # }
19
+ # RowKey "globals": {
20
+ # <key>: <marshal blob>,
21
+ # <key>: <marshal blob>,
22
+ # ...
23
+ # }
24
+ # }
25
+ # ]
26
+ #
27
+
28
+ module DCell
29
+ module Registry
30
+ class CassandraAdapter
31
+ DEFAULT_KEYSPACE = "dcell"
32
+ DEFAULT_CF = "dcell"
33
+
34
+ def initialize(options)
35
+ # Convert all options to symbols :/
36
+ options = options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
37
+
38
+ keyspace = options[:keyspace] || DEFAULT_KEYSPACE
39
+ columnfamily = options[:columnfamily] || DEFAULT_CF
40
+
41
+ options[:servers] ||= []
42
+ options[:servers] << options[:server] if options[:server]
43
+ options[:servers] << "localhost:9160" unless options[:servers].any?
44
+
45
+ cass = Cassandra.new(keyspace, options[:servers])
46
+
47
+ @global_registry = GlobalRegistry.new(cass, columnfamily)
48
+ end
49
+
50
+ def clear_globals
51
+ @global_registry.clear
52
+ end
53
+
54
+ class GlobalRegistry
55
+ def initialize(cass, cf)
56
+ @cass = cass
57
+ @cf = cf
58
+ end
59
+
60
+ def get(key)
61
+ string = @cass.get @cf, "globals", key.to_s
62
+ Marshal.load string if string
63
+ end
64
+
65
+ # Set a global value
66
+ def set(key, value)
67
+ string = Marshal.dump value
68
+ @cass.insert @cf, "globals", { key.to_s => string }
69
+ end
70
+
71
+ # The keys to all globals in the system
72
+ def global_keys
73
+ @cass.get(@cf, "globals").keys
74
+ end
75
+
76
+ def clear
77
+ @cass.del @cf, "globals"
78
+ end
79
+ end
80
+
81
+ def get_global(key); @global_registry.get(key) end
82
+ def set_global(key, value); @global_registry.set(key, value) end
83
+ def global_keys; @global_registry.global_keys end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,235 @@
1
+ require 'forwardable'
2
+ require 'uri'
3
+
4
+ module DCell
5
+ module Gossip
6
+ class VersionVector
7
+ extend Forwardable
8
+
9
+ class Status
10
+ STATES = [:precedes, :equal, :concurrent, :succeeds]
11
+
12
+ STATES.each do |state|
13
+ class_eval %Q{
14
+ def #{state}?
15
+ @state == '#{state}'.to_sym
16
+ end
17
+ def #{state}!
18
+ @state = '#{state}'.to_sym
19
+ end
20
+ }
21
+ end
22
+ end
23
+
24
+ def_delegators :@versions, :[]
25
+ attr_reader :versions
26
+
27
+ def initialize(id)
28
+ @versions = {}
29
+ update_at id
30
+ end
31
+
32
+ def update_at(id)
33
+ observe id
34
+ @versions[id] += 1
35
+ end
36
+
37
+ def observe(id)
38
+ @versions[id] ||= 0
39
+ end
40
+
41
+ def covers?(nodes, vector)
42
+ nodes.each do |node|
43
+ # We must have an entry for the node
44
+ return false unless @versions.keys.include? node
45
+
46
+ # And someone else must have seen the entry, too.
47
+ version = vector.versions[node] || 0
48
+ return false if @versions[node] != version
49
+ end
50
+ true
51
+ end
52
+
53
+ def compare(other)
54
+ v1_bigger = false
55
+ v2_bigger = false
56
+
57
+ @versions.each do |id, version|
58
+ if not other.versions.include? id
59
+ # Version vectors behave like vector clocks, with slightly
60
+ # different update rules. A vector clock assumes that all
61
+ # processes initially observe version 0. Since we don't
62
+ # know the topology ahead of time, we assume that a missing
63
+ # entry corresponds to a node that has not yet been discovered,
64
+ # and thus the version is implicitly 0.
65
+ v1_bigger = true if version > 0
66
+ else
67
+ v1_bigger = true if version > other.versions[id]
68
+ v2_bigger = true if version < other.versions[id]
69
+ end
70
+ end
71
+
72
+ other.versions.each do |id, version|
73
+ if not @versions.include? id
74
+ # See the comment above for the similar v1_bigger calculation.
75
+ v2_bigger = true if version > 0
76
+ else
77
+ v2_bigger = true if version > @versions[id]
78
+ v1_bigger = true if version < @versions[id]
79
+ end
80
+ end
81
+
82
+ status = Status.new
83
+ if !v1_bigger
84
+ if !v2_bigger
85
+ status.equal!
86
+ else
87
+ status.precedes!
88
+ end
89
+ elsif !v2_bigger
90
+ status.succeeds!
91
+ else
92
+ status.concurrent!
93
+ end
94
+ return status
95
+ end
96
+
97
+ # Take the entrywise maximum of the versions
98
+ def merge!(other)
99
+ @versions.each do |id, version|
100
+ if other.versions.include? id
101
+ @versions[id] = other.versions[id] if other.versions[id] > version
102
+ end
103
+ end
104
+
105
+ other.versions.each do |id, version|
106
+ if @versions.include? id
107
+ @versions[id] = @versions[id] if @versions[id] > version
108
+ end
109
+ end
110
+ @versions.merge!(other.versions.reject { |k,v| @versions.include? k })
111
+ end
112
+
113
+ def to_s
114
+ @versions.to_s
115
+ end
116
+ end
117
+
118
+ class Store
119
+ class Data
120
+ attr_reader :key, :value, :vector
121
+ def initialize(key, value, id)
122
+ @key = key
123
+ @value = value
124
+ @vector = VersionVector.new(id)
125
+ @changed = true
126
+ end
127
+
128
+ def clear
129
+ @vector.update_at DCell.id
130
+ @deleted = true
131
+ @value = nil
132
+ @changed = true
133
+ end
134
+
135
+ def deleted?
136
+ @deleted
137
+ end
138
+
139
+ def changed?
140
+ @changed
141
+ end
142
+
143
+ def observe
144
+ @vector.observe DCell.id
145
+ end
146
+
147
+ def value=(value)
148
+ if @value != value
149
+ @vector.update_at DCell.id
150
+ @value = value
151
+ @deleted = value.nil?
152
+ @changed = true
153
+ Celluloid::Logger.debug "Updated key #{key} to #{value}"
154
+ end
155
+ end
156
+
157
+ def merge!(other)
158
+ # We'll take other if we preceded it, or if we are
159
+ # concurrent with it (though issue a warning that data
160
+ # has been lost).
161
+ status = @vector.compare(other.vector)
162
+ if status.precedes? or status.concurrent?
163
+ if other.value != @value
164
+ if status.concurrent?
165
+ Celluloid::Logger.debug "Dropping local copy of concurrent data for #{@key}"
166
+ else
167
+ Celluloid::Logger.debug "Observed updated data #{key} => #{other.value}"
168
+ end
169
+ @value = other.value
170
+ @deleted = @value.nil?
171
+ end
172
+ elsif status.succeeds?
173
+ Celluloid::Logger.debug "Local data succeeds for #{@key}"
174
+ end
175
+ @vector.merge!(other.vector) unless status.equal?
176
+
177
+ # Stop gossiping if this has been seen by every known, healthy node
178
+ nodes = DCell::Node.all.map { |node| node.state == :connected }
179
+ @changed = false if @vector.covers?(nodes, other.vector)
180
+ end
181
+ end
182
+
183
+ def initialize(base_path)
184
+ @base_path = base_path
185
+ @data = {}
186
+ end
187
+
188
+ def path_for(key)
189
+ "#{@base_path}/#{key}"
190
+ end
191
+
192
+ def get(key)
193
+ data = @data[path_for(key)]
194
+ return data.value if data and not data.deleted?
195
+ nil
196
+ end
197
+
198
+ def set(key, value)
199
+ key = path_for(key)
200
+ if not @data[key]
201
+ @data[key] = Data.new(key, value, DCell.id)
202
+ else
203
+ @data[key].value = value
204
+ end
205
+ end
206
+
207
+ def observe(other)
208
+ key = other.key
209
+ if not @data[key]
210
+ @data[key] = other
211
+ @data[key].observe
212
+ Celluloid::Logger.debug "Observed new data #{key} => #{other.value}"
213
+ else
214
+ @data[key].merge!(other)
215
+ end
216
+ end
217
+
218
+ def keys
219
+ @data.keys.map { |k| k =~ /#{@base_path}\/(.+)$/; $1 }
220
+ end
221
+
222
+ def clear
223
+ @data.map(&:clear)
224
+ end
225
+
226
+ def changed
227
+ @data.each_value.select(&:changed?)
228
+ end
229
+
230
+ def values
231
+ @data.values
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,26 @@
1
+ require 'forwardable'
2
+
3
+ module DCell
4
+ module Registry
5
+ class GossipAdapter
6
+ extend Forwardable
7
+ PREFIX = "/dcell"
8
+
9
+ def_delegator :@global_registry, :get, :get_global
10
+ def_delegator :@global_registry, :set, :set_global
11
+ def_delegator :@global_registry, :clear, :clear_globals
12
+ def_delegator :@global_registry, :keys, :global_keys
13
+ def_delegators :@global_registry, :changed, :observe, :values
14
+
15
+ def initialize(options)
16
+ # Convert all options to symbols :/
17
+ options = options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
18
+
19
+ @env = options[:env] || 'production'
20
+ @base_path = options[:namespace] || "#{PREFIX}/#{@env}"
21
+
22
+ @global_registry = Gossip::Store.new("#{@base_path}/globals")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -16,7 +16,6 @@ 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)
20
19
  @global_registry = Registry.new(@moneta, :globals)
21
20
  end
22
21
 
@@ -39,7 +38,6 @@ module DCell
39
38
  end
40
39
 
41
40
  # DCell registry behaviors
42
- alias_method :nodes, :all
43
41
  alias_method :global_keys, :all
44
42
 
45
43
  def clear
@@ -47,11 +45,6 @@ module DCell
47
45
  end
48
46
  end
49
47
 
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
-
55
48
  def get_global(key); @global_registry.get(key) end
56
49
  def set_global(key, value); @global_registry.set(key, value) end
57
50
  def global_keys; @global_registry.global_keys end