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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +203 -0
- data/README.md +123 -0
- data/lib/aerospike.rb +69 -0
- data/lib/aerospike/aerospike_exception.rb +111 -0
- data/lib/aerospike/bin.rb +46 -0
- data/lib/aerospike/client.rb +649 -0
- data/lib/aerospike/cluster/cluster.rb +537 -0
- data/lib/aerospike/cluster/connection.rb +113 -0
- data/lib/aerospike/cluster/node.rb +248 -0
- data/lib/aerospike/cluster/node_validator.rb +85 -0
- data/lib/aerospike/cluster/partition.rb +54 -0
- data/lib/aerospike/cluster/partition_tokenizer_new.rb +128 -0
- data/lib/aerospike/cluster/partition_tokenizer_old.rb +135 -0
- data/lib/aerospike/command/batch_command.rb +120 -0
- data/lib/aerospike/command/batch_command_exists.rb +93 -0
- data/lib/aerospike/command/batch_command_get.rb +150 -0
- data/lib/aerospike/command/batch_item.rb +69 -0
- data/lib/aerospike/command/batch_node.rb +82 -0
- data/lib/aerospike/command/command.rb +680 -0
- data/lib/aerospike/command/delete_command.rb +57 -0
- data/lib/aerospike/command/execute_command.rb +42 -0
- data/lib/aerospike/command/exists_command.rb +57 -0
- data/lib/aerospike/command/field_type.rb +44 -0
- data/lib/aerospike/command/operate_command.rb +37 -0
- data/lib/aerospike/command/read_command.rb +174 -0
- data/lib/aerospike/command/read_header_command.rb +63 -0
- data/lib/aerospike/command/single_command.rb +60 -0
- data/lib/aerospike/command/touch_command.rb +50 -0
- data/lib/aerospike/command/write_command.rb +60 -0
- data/lib/aerospike/host.rb +43 -0
- data/lib/aerospike/info.rb +96 -0
- data/lib/aerospike/key.rb +99 -0
- data/lib/aerospike/language.rb +25 -0
- data/lib/aerospike/ldt/large.rb +69 -0
- data/lib/aerospike/ldt/large_list.rb +100 -0
- data/lib/aerospike/ldt/large_map.rb +82 -0
- data/lib/aerospike/ldt/large_set.rb +78 -0
- data/lib/aerospike/ldt/large_stack.rb +72 -0
- data/lib/aerospike/loggable.rb +55 -0
- data/lib/aerospike/operation.rb +70 -0
- data/lib/aerospike/policy/client_policy.rb +37 -0
- data/lib/aerospike/policy/generation_policy.rb +37 -0
- data/lib/aerospike/policy/policy.rb +54 -0
- data/lib/aerospike/policy/priority.rb +34 -0
- data/lib/aerospike/policy/record_exists_action.rb +45 -0
- data/lib/aerospike/policy/write_policy.rb +61 -0
- data/lib/aerospike/record.rb +42 -0
- data/lib/aerospike/result_code.rb +353 -0
- data/lib/aerospike/task/index_task.rb +59 -0
- data/lib/aerospike/task/task.rb +71 -0
- data/lib/aerospike/task/udf_register_task.rb +55 -0
- data/lib/aerospike/task/udf_remove_task.rb +55 -0
- data/lib/aerospike/udf.rb +24 -0
- data/lib/aerospike/utils/buffer.rb +139 -0
- data/lib/aerospike/utils/epoc.rb +28 -0
- data/lib/aerospike/utils/pool.rb +65 -0
- data/lib/aerospike/value/particle_type.rb +45 -0
- data/lib/aerospike/value/value.rb +380 -0
- data/lib/aerospike/version.rb +4 -0
- 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
|