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