aerospike 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE +203 -0
  4. data/README.md +123 -0
  5. data/lib/aerospike.rb +69 -0
  6. data/lib/aerospike/aerospike_exception.rb +111 -0
  7. data/lib/aerospike/bin.rb +46 -0
  8. data/lib/aerospike/client.rb +649 -0
  9. data/lib/aerospike/cluster/cluster.rb +537 -0
  10. data/lib/aerospike/cluster/connection.rb +113 -0
  11. data/lib/aerospike/cluster/node.rb +248 -0
  12. data/lib/aerospike/cluster/node_validator.rb +85 -0
  13. data/lib/aerospike/cluster/partition.rb +54 -0
  14. data/lib/aerospike/cluster/partition_tokenizer_new.rb +128 -0
  15. data/lib/aerospike/cluster/partition_tokenizer_old.rb +135 -0
  16. data/lib/aerospike/command/batch_command.rb +120 -0
  17. data/lib/aerospike/command/batch_command_exists.rb +93 -0
  18. data/lib/aerospike/command/batch_command_get.rb +150 -0
  19. data/lib/aerospike/command/batch_item.rb +69 -0
  20. data/lib/aerospike/command/batch_node.rb +82 -0
  21. data/lib/aerospike/command/command.rb +680 -0
  22. data/lib/aerospike/command/delete_command.rb +57 -0
  23. data/lib/aerospike/command/execute_command.rb +42 -0
  24. data/lib/aerospike/command/exists_command.rb +57 -0
  25. data/lib/aerospike/command/field_type.rb +44 -0
  26. data/lib/aerospike/command/operate_command.rb +37 -0
  27. data/lib/aerospike/command/read_command.rb +174 -0
  28. data/lib/aerospike/command/read_header_command.rb +63 -0
  29. data/lib/aerospike/command/single_command.rb +60 -0
  30. data/lib/aerospike/command/touch_command.rb +50 -0
  31. data/lib/aerospike/command/write_command.rb +60 -0
  32. data/lib/aerospike/host.rb +43 -0
  33. data/lib/aerospike/info.rb +96 -0
  34. data/lib/aerospike/key.rb +99 -0
  35. data/lib/aerospike/language.rb +25 -0
  36. data/lib/aerospike/ldt/large.rb +69 -0
  37. data/lib/aerospike/ldt/large_list.rb +100 -0
  38. data/lib/aerospike/ldt/large_map.rb +82 -0
  39. data/lib/aerospike/ldt/large_set.rb +78 -0
  40. data/lib/aerospike/ldt/large_stack.rb +72 -0
  41. data/lib/aerospike/loggable.rb +55 -0
  42. data/lib/aerospike/operation.rb +70 -0
  43. data/lib/aerospike/policy/client_policy.rb +37 -0
  44. data/lib/aerospike/policy/generation_policy.rb +37 -0
  45. data/lib/aerospike/policy/policy.rb +54 -0
  46. data/lib/aerospike/policy/priority.rb +34 -0
  47. data/lib/aerospike/policy/record_exists_action.rb +45 -0
  48. data/lib/aerospike/policy/write_policy.rb +61 -0
  49. data/lib/aerospike/record.rb +42 -0
  50. data/lib/aerospike/result_code.rb +353 -0
  51. data/lib/aerospike/task/index_task.rb +59 -0
  52. data/lib/aerospike/task/task.rb +71 -0
  53. data/lib/aerospike/task/udf_register_task.rb +55 -0
  54. data/lib/aerospike/task/udf_remove_task.rb +55 -0
  55. data/lib/aerospike/udf.rb +24 -0
  56. data/lib/aerospike/utils/buffer.rb +139 -0
  57. data/lib/aerospike/utils/epoc.rb +28 -0
  58. data/lib/aerospike/utils/pool.rb +65 -0
  59. data/lib/aerospike/value/particle_type.rb +45 -0
  60. data/lib/aerospike/value/value.rb +380 -0
  61. data/lib/aerospike/version.rb +4 -0
  62. metadata +132 -0
@@ -0,0 +1,113 @@
1
+ # encoding: utf-8
2
+ # Copyright 2014 Aerospike, Inc.
3
+ #
4
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
+ # license agreements.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
8
+ # use this file except in compliance with the License. You may obtain a copy of
9
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations under
15
+ # the License.
16
+
17
+ require 'socket'
18
+
19
+ module Aerospike
20
+
21
+ private
22
+
23
+ class Connection
24
+
25
+ def initialize(host, port, timeout = 30)
26
+
27
+ connect(host, port, timeout).tap do |socket|
28
+ @socket = socket
29
+ @timeout = timeout
30
+ end
31
+
32
+ self
33
+ end
34
+
35
+ def connect(host, port, timeout)
36
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
37
+ sockaddr = Socket.sockaddr_in(port, host)
38
+ begin
39
+ socket.connect_nonblock(sockaddr)
40
+ rescue Errno::EINPROGRESS
41
+ # Block until the socket is ready, then try again
42
+ IO.select([socket], [socket], [socket], timeout.to_f)
43
+ @sockaddr = sockaddr
44
+ return socket
45
+ end
46
+ end
47
+
48
+ def write(buffer, length)
49
+ total = 0
50
+ while total < length
51
+ begin
52
+ written = @socket.write_nonblock(buffer.read(total, length - total))
53
+ total += written
54
+ rescue IO::WaitWriteable, Errno::EAGAIN
55
+ IO.select(nil, [@socket])
56
+ retry
57
+ rescue => e
58
+ Aerospike::Exceptions::Connection.new("#{e}")
59
+ end
60
+ end
61
+ end
62
+
63
+ def read(buffer, length)
64
+ total = 0
65
+ while total < length
66
+ begin
67
+ bytes = @socket.recv_nonblock(length - total)
68
+ buffer.write_binary(bytes, total) if bytes.bytesize > 0
69
+ total += bytes.bytesize
70
+ rescue IO::WaitReadable, Errno::EAGAIN
71
+ IO.select([@socket], nil)
72
+ retry
73
+ rescue => e
74
+ Aerospike::Exceptions::Connection.new("#{e}")
75
+ end
76
+ end
77
+ end
78
+
79
+ def connected?
80
+ @socket != nil
81
+ end
82
+
83
+ def valid?
84
+ @socket != nil
85
+ end
86
+
87
+ def close
88
+ @socket.close if @socket
89
+ @socket = nil
90
+ end
91
+
92
+ def timeout=(timeout)
93
+ if timeout > 0 && timeout != @timeout
94
+ @timeout = timeout
95
+ # if IO.select([@socket], [@socket], [@socket], timeout.to_f)
96
+ if IO.select(nil, nil, nil, timeout.to_f)
97
+ begin
98
+ # Verify there is now a good connection
99
+ @socket.connect_nonblock(@sockaddr)
100
+ rescue Errno::EISCONN
101
+ # operation successful
102
+ rescue => e
103
+ # An unexpected exception was raised - the connection is no good.
104
+ close
105
+ raise Aerospike::Exceptions::Connection.new("#{e}")
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ end
@@ -0,0 +1,248 @@
1
+ # encoding: utf-8
2
+ # Copyright 2014 Aerospike, Inc.
3
+ #
4
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
+ # license agreements.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
8
+ # use this file except in compliance with the License. You may obtain a copy of
9
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations under
15
+ # the License.
16
+
17
+ require 'atomic'
18
+
19
+ module Aerospike
20
+
21
+ private
22
+
23
+ class Node
24
+
25
+ attr_reader :reference_count, :responded, :name
26
+
27
+ PARTITIONS = 4096
28
+ FULL_HEALTH = 100
29
+
30
+ # Initialize server node with connection parameters.
31
+ def initialize(cluster, nv)
32
+ @cluster = cluster
33
+ @name = nv.name
34
+ @aliases = Atomic.new(nv.aliases)
35
+ @host = nv.host
36
+ @use_new_info = Atomic.new(nv.use_new_info)
37
+
38
+ # Assign host to first IP alias because the server identifies nodes
39
+ # by IP address (not hostname).
40
+ @host = nv.aliases[0]
41
+ @health = Atomic.new(FULL_HEALTH)
42
+ @partition_generation = Atomic.new(-1)
43
+ @reference_count = Atomic.new(0)
44
+ @responded = Atomic.new(false)
45
+ @active = Atomic.new(true)
46
+
47
+ @connections = Pool.new(@cluster.connection_queue_size)
48
+ @connections.create_block = Proc.new do
49
+ while conn = Connection.new(@host.name, @host.port, @cluster.connection_timeout)
50
+ break if conn.connected?
51
+ end
52
+ conn
53
+ end
54
+
55
+ @connections.cleanup_block = Proc.new { |conn| conn.close }
56
+ end
57
+
58
+ # Request current status from server node, and update node with the result
59
+ def refresh
60
+ friends = []
61
+ conn = get_connection(1)
62
+
63
+ begin
64
+ info_map = Info.request(conn, "node", "partition-generation", "services")
65
+ rescue => e
66
+ conn.close
67
+ decrease_health
68
+
69
+ raise e
70
+ end
71
+
72
+ verify_node_name(info_map)
73
+ restore_health
74
+
75
+ @responded.update{|v| true}
76
+
77
+ friends = add_friends(info_map)
78
+ update_partitions(conn, info_map)
79
+ put_connection(conn)
80
+ friends
81
+ end
82
+
83
+ # Get a connection to the node. If no cached connection is not available,
84
+ # a new connection will be created
85
+ def get_connection(timeout)
86
+ while true
87
+ conn = @connections.poll
88
+ if conn.connected?
89
+ conn.timeout = timeout.to_f
90
+ return conn
91
+ end
92
+ end
93
+ end
94
+
95
+ # Put back a connection to the cache. If cache is full, the connection will be
96
+ # closed and discarded
97
+ def put_connection(conn)
98
+ conn.close if !@active.value
99
+ @connections.offer(conn)
100
+ end
101
+
102
+ # Mark the node as healthy
103
+ def restore_health
104
+ # There can be cases where health is full, but active is false.
105
+ # Once a node has been marked inactive, it stays inactive.
106
+ @health.value = FULL_HEALTH
107
+ end
108
+
109
+ # Decrease node Health as a result of bad connection or communication
110
+ def decrease_health
111
+ @health.update {|v| v -= 1 }
112
+ end
113
+
114
+ # Check if the node is unhealthy
115
+ def unhealthy?
116
+ @health.value <= 0
117
+ end
118
+
119
+ # Retrieves host for the node
120
+ def get_host
121
+ @host
122
+ end
123
+
124
+ # Checks if the node is active
125
+ def active?
126
+ @active.value
127
+ end
128
+
129
+ # Returns node name
130
+ def get_name
131
+ @name
132
+ end
133
+
134
+ # Returns node aliases
135
+ def get_aliases
136
+ @aliases.value
137
+ end
138
+
139
+ # Adds an alias for the node
140
+ def add_alias(alias_to_add)
141
+ # Aliases are only referenced in the cluster tend threads,
142
+ # so synchronization is not necessary.
143
+ aliases = get_aliases
144
+ aliases ||= []
145
+
146
+ aliases << alias_to_add
147
+ set_aliases(aliases)
148
+ end
149
+
150
+ # Marks node as inactice and closes all cached connections
151
+ def close
152
+ @active.value = false
153
+ close_connections
154
+ end
155
+
156
+ # Implements stringer interface
157
+ def to_s
158
+ "#{@name}:#{@host}"
159
+ end
160
+
161
+ def ==(other)
162
+ other && other.is_a?(Node) && (@name == other.name)
163
+ end
164
+ alias eql? ==
165
+
166
+ def use_new_info?
167
+ @use_new_info.value
168
+ end
169
+
170
+ def hash
171
+ @name.hash
172
+ end
173
+
174
+ private
175
+
176
+ def close_connections
177
+ # drain connections and close all of them
178
+ # non-blocking, does not call create_block when passed false
179
+ while conn = @connections.poll(false)
180
+ conn.close
181
+ end
182
+ end
183
+
184
+
185
+ # Sets node aliases
186
+ def set_aliases(aliases)
187
+ @aliases.value = aliases
188
+ end
189
+
190
+ def verify_node_name(info_map)
191
+ info_name = info_map['node']
192
+
193
+ if !info_name
194
+ decrease_health
195
+ raise Aerospike::Exceptions.Aerospike.new("Node name is empty")
196
+ end
197
+
198
+ if !(@name == info_name)
199
+ # Set node to inactive immediately.
200
+ @active.update{|v| false}
201
+ raise Aerospike::Exceptions.Aerospike.new("Node name has changed. Old=#{@name} New= #{info_name}")
202
+ end
203
+ end
204
+
205
+ def add_friends(info_map)
206
+ friend_string = info_map['services']
207
+ friends = []
208
+
209
+ return [] if friend_string.to_s.empty?
210
+
211
+ friend_names = friend_string.split(';')
212
+
213
+ friend_names.each do |friend|
214
+ friend_info = friend.split(':')
215
+ host = friend_info[0]
216
+ port = friend_info[1].to_i
217
+ aliass = Host.new(host, port)
218
+ node = @cluster.find_alias(aliass)
219
+
220
+ if node
221
+ node.reference_count.update{|v| v + 1}
222
+ else
223
+ unless friends.any? {|host| host == aliass}
224
+ friends << aliass
225
+ end
226
+ end
227
+ end
228
+
229
+ friends
230
+ end
231
+
232
+ def update_partitions(conn, info_map)
233
+ gen_string = info_map['partition-generation']
234
+
235
+ raise Aerospike::Exceptions::Parse.new("partition-generation is empty") if gen_string.to_s.empty?
236
+
237
+ generation = gen_string.to_i
238
+
239
+ if @partition_generation.value != generation
240
+ Aerospike.logger.info("Node #{get_name} partition generation #{generation} changed")
241
+ @cluster.update_partitions(conn, self)
242
+ @partition_generation.value = generation
243
+ end
244
+ end
245
+
246
+ end # class Node
247
+
248
+ end # module
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+ # Copyright 2014 Aerospike, Inc.
3
+ #
4
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
+ # license agreements.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
8
+ # use this file except in compliance with the License. You may obtain a copy of
9
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations under
15
+ # the License.
16
+
17
+ module Aerospike
18
+
19
+ private
20
+
21
+ class NodeValidator
22
+
23
+ attr_reader :host, :aliases, :name, :use_new_info
24
+
25
+ def initialize(host, timeout)
26
+ @use_new_info = true
27
+ @host = host
28
+
29
+ set_aliases(host)
30
+ set_address(timeout)
31
+
32
+ self
33
+ end
34
+
35
+ def set_aliases(host)
36
+ addresses = Resolv.getaddresses(host.name)
37
+ aliases = []
38
+ addresses.each do |addr|
39
+ aliases << Host.new(addr, host.port)
40
+ end
41
+
42
+ @aliases = aliases
43
+
44
+ Aerospike.logger.debug("Node Validator has #{aliases.length} nodes.")
45
+ end
46
+
47
+ def set_address(timeout)
48
+ @aliases.each do |aliass|
49
+ begin
50
+ conn = Connection.new(aliass.name, aliass.port, 1)
51
+ conn.timeout = timeout
52
+
53
+ info_map= Info.request(conn, 'node', 'build')
54
+ if node_name = info_map['node']
55
+ @name = node_name
56
+
57
+ # Check new info protocol support for >= 2.6.6 build
58
+ if build_version = info_map['build']
59
+ v1, v2, v3 = parse_version_string(build_version)
60
+ @use_new_info = v1.to_i > 2 || (v1.to_i == 2 && (v2.to_i > 6 || (v2.to_i == 6 && v3.to_i >= 6)))
61
+ end
62
+ end
63
+ ensure
64
+ conn.close if conn
65
+ end
66
+
67
+ end
68
+ end
69
+
70
+ protected
71
+
72
+ # parses a version string
73
+ @@version_regexp = /(?<v1>\d+)\.(?<v2>\d+)\.(?<v3>\d+).*/
74
+
75
+ def parse_version_string(version)
76
+ if v = @@version_regexp.match(version)
77
+ return v['v1'], v['v2'], v['v3']
78
+ end
79
+
80
+ raise Aerospike::Exceptions::Parse.new("Invalid build version string in Info: #{version}")
81
+ end
82
+
83
+ end # class
84
+
85
+ end #module
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ # Copyright 2014 Aerospike, Inc.
3
+ #
4
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
+ # license agreements.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
8
+ # use this file except in compliance with the License. You may obtain a copy of
9
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations under
15
+ # the License.
16
+
17
+ module Aerospike
18
+
19
+ private
20
+
21
+ class Partition
22
+ attr_reader :namespace, :partition_id
23
+
24
+ def initialize(namespace, partition_id)
25
+ @namespace = namespace
26
+ @partition_id = partition_id
27
+
28
+ self
29
+ end
30
+
31
+ def self.new_by_key(key)
32
+ Partition.new(
33
+ key.namespace,
34
+ (key.digest[0..3].unpack('l<')[0] & 0xFFFF) % Node::PARTITIONS
35
+ )
36
+ end
37
+
38
+ def to_s
39
+ "#{@namespace}:#{partition_id}"
40
+ end
41
+
42
+ def ==(other)
43
+ other && other.is_a?(Partition) && @partition_id == other.partition_id &&
44
+ @namespace == other.namespace
45
+ end
46
+ alias eql? ==
47
+
48
+ def hash
49
+ to_s.hash
50
+ end
51
+
52
+ end # class
53
+
54
+ end # module