adhd 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,5 +3,5 @@
3
3
  node_url: "192.168.1.104"
4
4
  couchdb_server_port: 5984
5
5
  buddy_server_url: "http://192.168.1.104:5984" # It is a good idea if this is a management node
6
- buddy_server_name: foo
6
+ buddy_server_db_name: foo
7
7
 
@@ -0,0 +1,17 @@
1
+ class ContentDoc < CouchRest::ExtendedDocument
2
+ # NOTE: NO DEFAULT DATABASE IN THE OBJECT -- WE WILL BE STORING A LOT OF
3
+ # DATABASES OF THIS TYPE.
4
+
5
+
6
+ property :_id
7
+ property :internal_id
8
+ property :size_bytes
9
+ property :filename
10
+ property :mime_type
11
+
12
+ view_by :internal_id
13
+
14
+ # A special attachment "File" is expected to exist
15
+
16
+ end
17
+
@@ -0,0 +1,97 @@
1
+
2
+
3
+ class ContentShard
4
+ attr_accessor :nodes, :this_shard, :our_node, :this_shard_db
5
+
6
+ def initialize(nodesv, this_shardv)
7
+ @nodes = nodesv
8
+ @this_shard = this_shardv
9
+
10
+ # Work out the rest
11
+ @our_node = nodesv.our_node
12
+ @this_shard_db = nodesv.our_node.get_content_db(this_shardv.shard_db_name)
13
+
14
+ @last_sync_seq = 0 # @this_shard_db.info['update_seq']
15
+ end
16
+
17
+ def in_shard?(internal_id)
18
+ internal_id >= this_shard.range_start && internal_id < this_shard.range_end
19
+ end
20
+
21
+ def write_doc(content_doc)
22
+ # Write a content document to this shard
23
+ # Make sure it is in this shard
24
+ if in_shard? content_doc.internal_id
25
+ this_shard_db.save_doc(content_doc)
26
+ end
27
+ end
28
+
29
+ def sync
30
+ # A Shard only pushes with the master of the shard
31
+ # or the node with the highest is_storage value alive
32
+ # Shard masters ensure changes are pushed to all
33
+
34
+ # NOTE: This method needs serious refactoring
35
+ # No need to update
36
+ return false if @this_shard_db.info['update_seq'] == @last_sync_seq
37
+
38
+ # Are we the shard master?
39
+ am_master = (our_node.name == this_shard.master_node)
40
+
41
+ if !am_master
42
+ master_node = Node.by_name(:key => this_shard.master_node).first
43
+ remote_db = master_node.get_content_db(this_shard.shard_db_name)
44
+ bool_to = @our_node.replicate_to(this_shard_db, master_node, remote_db)
45
+ if bool_to
46
+ @last_sync_seq = @this_shard_db.info['update_seq']
47
+ return true
48
+ end
49
+ end
50
+
51
+ # Either we are the master or the master has failed -- we replicate with
52
+ # all nodes or the first available aside us and master
53
+ all_good = true
54
+ this_shard.node_list.each do |node_name|
55
+ # Push all changes to the other nodes
56
+ remote_node = Node.by_name(:key => node_name).first
57
+ remote_db = remote_node.get_content_db(this_shard.shard_db_name)
58
+ all_good &= @our_node.replicate_to(this_shard_db, remote_node, remote_db)
59
+
60
+ if !am_master && bool_to
61
+ # NOTE: How to build skynet, Note 2
62
+ # We are doing some "gonzo" replication, here. Our master is
63
+ # clearly down so we find the second best node; we push our
64
+ # changes to this node, and now also *replicate from*
65
+ # that node.
66
+ @our_node.replicate_from(this_shard_db, remote_node, remote_db)
67
+ @last_sync_seq = @this_shard_db.info['update_seq']
68
+ break
69
+ end
70
+ end
71
+ if all_good
72
+ @last_sync_seq = @this_shard_db.info['update_seq']
73
+ return true
74
+ else
75
+ return false
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ class ContentDoc < CouchRest::ExtendedDocument
82
+ # NOTE: NO DEFAULT DATABASE IN THE OBJECT -- WE WILL BE STORING A LOT OF
83
+ # DATABASES OF THIS TYPE.
84
+
85
+
86
+ property :_id
87
+ property :internal_id
88
+ property :size_bytes
89
+ property :filename
90
+ property :mime_type
91
+
92
+ view_by :internal_id
93
+
94
+ # A special attachment "File" is expected to exist
95
+
96
+ end
97
+
@@ -0,0 +1,139 @@
1
+ ## Key Restrictions ok internal_IDs: must only contain [a-z0-9-]
2
+
3
+ class NodeDB
4
+
5
+ attr_accessor :local_node_db, :our_node
6
+
7
+ def initialize(our_nodev)
8
+ @our_node = our_nodev
9
+
10
+ # Get the address of the CDB from the node
11
+ @local_node_db = our_nodev.get_node_db
12
+ end
13
+
14
+ def sync
15
+ # We replicate our state to the management node(s)
16
+ management_nodes = Node.by_is_management.reverse
17
+ # NOTE: randomize the order for load balancing here
18
+
19
+ # NOTE2: How to build skynet (TODO)
20
+ # -------------------
21
+ # If length of management is zero, then chose 3 different random
22
+ # nodes at each sync, and sync with them in node_name order.
23
+ # This guarantees that any updates on nodes are communicated in
24
+ # O(log N) ephocs, at the cost of O(3 * N) connections per epoch.
25
+ # It also guarantees any new management servers are discovered in
26
+ # this O(log N) time, creating "jelly fish" or "partition proof"
27
+ # availability.
28
+
29
+ management_nodes.each do |mng_node|
30
+ remote_db = mng_node.get_node_db
31
+ bool_from = @our_node.replicate_from(local_node_db, mng_node, remote_db)
32
+ bool_to = @our_node.replicate_to(local_node_db, mng_node, remote_db)
33
+ if bool_from && bool_to && !our_node.is_management
34
+ #puts "Pushed to management"
35
+ break
36
+ end
37
+ #puts "Did not push to management"
38
+ end
39
+ end
40
+
41
+ def available_node_list
42
+ # Returns all nodes marked as available
43
+ all_nodes = Node.by_name
44
+ return all_nodes.select {|node| node.status == "RUNNING"}
45
+ end
46
+
47
+ def head_management_node
48
+ management_nodes = Node.by_is_management.reverse
49
+ hmn = management_nodes.find {|node| node.status == "RUNNING"}
50
+ return hmn
51
+ end
52
+
53
+ end
54
+
55
+ class Node < CouchRest::ExtendedDocument
56
+ #NODESERVER = CouchRest.new("#{ARGV[1]}")
57
+ #NODESERVER.default_database = "#{ARGV[0]}_node_db"
58
+
59
+ #use_database NODESERVER.default_database
60
+
61
+ unique_id :name
62
+
63
+ property :name
64
+ property :url
65
+ property :is_store
66
+ property :is_management
67
+ property :is_directory
68
+ property :status
69
+
70
+ timestamps!
71
+
72
+ view_by :name
73
+ view_by :is_management
74
+
75
+ def get_node_db
76
+ server = CouchRest.new("#{url}")
77
+ db = server.database!("#{name}_node_db")
78
+ # puts "Open db #{db}"
79
+ db
80
+ end
81
+
82
+ def get_shard_db
83
+ server = CouchRest.new("#{url}")
84
+ db = server.database!("#{name}_shard_db")
85
+ # puts "Open db #{db}"
86
+ db
87
+ end
88
+
89
+ def get_content_db(shard_db_name)
90
+ server = CouchRest.new("#{url}")
91
+ db = server.database!("#{name}_#{shard_db_name}_content_db")
92
+ # puts "Open db #{db}"
93
+ db
94
+ end
95
+
96
+ # Replicating databases and marking nodes as unavailable
97
+ # In the future we should hook these into a "replication manager"
98
+ # for databases. The manager should set up continuous replication across
99
+ # databases, and only do a replication after some time lapses.
100
+
101
+ def replicate_to(local_db, other_node, remote_db)
102
+ # Do not try to contact unavailable nodes
103
+ return false if other_node.status == "UNAVAILABLE"
104
+ # No point replicating to ourselves
105
+ return false if (name == other_node.name)
106
+
107
+ begin
108
+ # Replicate to other node is possible
109
+ local_db.replicate_to(remote_db)
110
+ return true
111
+ rescue Exception => e
112
+ # Other node turns out to be unavailable
113
+ other_node.status = "UNAVAILABLE"
114
+ other_node.save
115
+ return false
116
+ end
117
+ end
118
+
119
+ def replicate_from(local_db, other_node, remote_db)
120
+ # Do not try to contact unavailable nodes
121
+ return false if other_node.status == "UNAVAILABLE"
122
+ # No point replicating to ourselves
123
+ return false if (name == other_node.name)
124
+
125
+ begin
126
+ # Replicate to other node is possible
127
+ local_db.replicate_from(remote_db)
128
+ return true
129
+ rescue Exception => e
130
+ # Other node turns out to be unavailable
131
+ other_node.status = "UNAVAILABLE"
132
+ other_node.save
133
+ return false
134
+ end
135
+
136
+ end
137
+
138
+ end
139
+
@@ -0,0 +1,202 @@
1
+ class ShardRangeDB
2
+
3
+ attr_accessor :nodes, :local_shard_db, :our_node
4
+
5
+ def initialize(nodesv)
6
+ @nodes = nodesv
7
+
8
+ # Automatically get our shard_db address from our own node name
9
+ @our_node = nodesv.our_node
10
+ @local_shard_db = nodesv.our_node.get_shard_db
11
+
12
+ puts "Assign default database for shard ranges (#{@local_shard_db})"
13
+ ShardRange.use_database @local_shard_db
14
+ end
15
+
16
+ def sync
17
+ # We replicate our state from the management node(s)
18
+ # We never push content if we are only storage
19
+ management_nodes = Node.by_is_management.reverse
20
+
21
+ # NOTE: randomize the order for load balancing here
22
+
23
+ management_nodes.each do |mng_node|
24
+ remote_db = mng_node.get_shard_db
25
+ bool_from = @our_node.replicate_from(local_shard_db, mng_node, remote_db)
26
+ if our_node.is_management
27
+ # Push any changes to other management nodes
28
+ bool_to = @our_node.replicate_to(local_shard_db, mng_node, remote_db)
29
+ end
30
+ break if bool_from && !our_node.is_management
31
+ end
32
+ end
33
+
34
+ def build_shards(number)
35
+ # Make a large list of possible id boundaries
36
+ characters = []
37
+ ("0".."9").each do |c|
38
+ characters << c
39
+ end
40
+ ("a".."f").each do |c|
41
+ characters << c
42
+ end
43
+
44
+ # Generate 36 x 36 keys to choose boundaries from
45
+ all_keys = []
46
+ characters.each do |c1|
47
+ characters.each do |c2|
48
+ characters.each do |c3|
49
+ all_keys << (c1+c2+c3)
50
+ end
51
+ end
52
+ end
53
+
54
+ # Now chose our boundaries
55
+ num_range_keys = all_keys.length
56
+ approx_shard_size = (num_range_keys * 1.0) / number
57
+
58
+ shard_starts = []
59
+ (0...number).each do |n|
60
+ shard_starts << (all_keys[(n * approx_shard_size).floor])
61
+ end
62
+
63
+ shard_ends = shard_starts.clone
64
+ shard_ends << ("z" * 3)
65
+ shard_ends.delete_at(0)
66
+
67
+ # Finally build them!
68
+ puts "Build Shards"
69
+ (0...number).each do |n|
70
+ puts "Shard #{n}: from #{shard_starts[n]} to #{shard_ends[n]}"
71
+ shard_name = "sh_#{shard_starts[n]}_to_#{shard_ends[n]}"
72
+ sr = ShardRange.new
73
+ sr.range_start = shard_starts[n]
74
+ sr.range_end = shard_ends[n]
75
+ sr.shard_db_name = shard_name
76
+ sr.save
77
+ end
78
+ end
79
+
80
+ def get_shard(internal_id)
81
+ # Finds the list of shards within which this ID lives
82
+ all_shards = ShardRange.by_range_start
83
+ selected_shards = []
84
+ all_shards.each do |a_shard| # TODO: linear search is inefficient -- create a view
85
+ if a_shard.range_start <= internal_id && a_shard.range_end > internal_id
86
+ selected_shards << a_shard
87
+ end
88
+ end
89
+ selected_shards
90
+ end
91
+
92
+ def get_content_shards
93
+ # Return the content_shards of our node
94
+ content_shards = {}
95
+ ShardRange.by_node(:key => our_node.name).each do |s|
96
+
97
+ # Build a content shard object
98
+ content_shards[s.shard_db_name] = ContentShard.new(nodes, s)
99
+ end
100
+ puts "Content shards #{content_shards.length}"
101
+ content_shards
102
+ end
103
+
104
+ def write_doc_directly(content_doc)
105
+ # Write a document directly to a nodes content repository
106
+ success = {:ok => false , :reason => "No available node found"}
107
+ doc_shard = get_shard(content_doc.internal_id).first
108
+ doc_shard.get_nodes.each do |node|
109
+ # Try to write the doc to this node
110
+ begin
111
+ remote_node = Node.by_name(:key => node).first
112
+ remote_ndb = NodeDB.new(remote_node)
113
+ remote_content_shard = ContentShard.new(remote_ndb, doc_shard)
114
+ remote_content_shard.this_shard_db.save_doc(content_doc)
115
+ success = {:ok => true, :doc => content_doc, :db => remote_content_shard.this_shard_db}
116
+ break
117
+ rescue RestClient::RequestFailed => rf
118
+ if rf.http_code == 409
119
+ puts "Document already there"
120
+ return {:ok => false , :reason => "Document already in database"}
121
+ end
122
+ rescue Exception =>e
123
+ puts "Could not put doc in node #{node} because of #{rf}"
124
+ # TODO: change status or chose another management server
125
+ remote_node.status = "UNAVAILABLE"
126
+ remote_node.save
127
+ end
128
+ end
129
+ return success
130
+ end
131
+
132
+ def get_doc_directly(internal_id)
133
+ # Write a document directly to a nodes content repository
134
+ doc_shard = get_shard(internal_id).first
135
+
136
+ # TODO: Randomize the order of nodes for load balancing in retrieval!
137
+ docx = []
138
+ doc_shard.get_nodes.each do |node|
139
+ # Try to write the doc to this node
140
+ begin
141
+ remote_node = Node.by_name(:key => node).first
142
+ remote_ndb = NodeDB.new(remote_node)
143
+ remote_content_shard = ContentShard.new(remote_ndb, doc_shard)
144
+
145
+ docx = ContentDoc.by_internal_id(:key => internal_id, :database => remote_content_shard.this_shard_db)
146
+ if docx.length > 0
147
+ return {:ok => true, :doc => docx.first, :db => remote_content_shard.this_shard_db }
148
+ end
149
+ rescue
150
+ puts "Could not put doc in node #{node.name}"
151
+ # TODO: change status or chose another management server
152
+ remote_node.status = "UNAVAILABLE"
153
+ remote_node.save
154
+ end
155
+ end
156
+ return {:ok => false }
157
+ end
158
+
159
+ end
160
+
161
+ class ShardRange < CouchRest::ExtendedDocument
162
+ # SHARDSERVER = CouchRest.new("#{ARGV[1]}")
163
+ # SHARDSERVER.default_database = "#{ARGV[0]}_shard_db"
164
+ # use_database SHARDSERVER.default_database
165
+
166
+ property :range_start
167
+ property :range_end
168
+ property :node_list
169
+ property :master_node
170
+ property :shard_db_name
171
+
172
+ view_by :range_start
173
+
174
+ # View "node" - given a node returns the shards watched
175
+ # How to use this new
176
+ #
177
+ # puts "Which Shards does 'node1' watch?"
178
+ # ShardRange.by_node(:key => "node1").each do |s|
179
+ # puts "Shard: #{s.shard_db_name}"
180
+ # end
181
+
182
+
183
+ view_by :node,
184
+ :map =>
185
+ "function(doc) {
186
+ if (doc['couchrest-type'] == 'ShardRange' && doc.node_list) {
187
+ doc.node_list.forEach(function(node){
188
+ emit(node, 1);
189
+ });
190
+ }
191
+ }"
192
+
193
+ def get_nodes
194
+ # Return all nodes, with the master being first
195
+ all_nodes = node_list.clone
196
+ all_nodes.delete(master_node)
197
+ all_nodes = [master_node] + all_nodes
198
+ all_nodes
199
+ end
200
+
201
+ end
202
+