adhd 0.0.1 → 0.1.0

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,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
+