riak-client 1.0.0.beta → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -4
- data/Gemfile +12 -17
- data/Guardfile +1 -1
- data/LICENSE +16 -0
- data/README.markdown +178 -0
- data/RELEASE_NOTES.md +99 -0
- data/Rakefile +25 -1
- data/erl_src/riak_kv_test014_backend.beam +0 -0
- data/erl_src/riak_kv_test014_backend.erl +189 -0
- data/erl_src/riak_kv_test_backend.beam +0 -0
- data/erl_src/riak_kv_test_backend.erl +37 -19
- data/lib/riak/client.rb +322 -272
- data/lib/riak/client/beefcake_protobuffs_backend.rb +6 -10
- data/lib/riak/client/decaying.rb +28 -0
- data/lib/riak/client/excon_backend.rb +27 -11
- data/lib/riak/client/http_backend.rb +71 -2
- data/lib/riak/client/http_backend/configuration.rb +17 -3
- data/lib/riak/client/http_backend/transport_methods.rb +3 -3
- data/lib/riak/client/net_http_backend.rb +18 -14
- data/lib/riak/client/node.rb +111 -0
- data/lib/riak/client/pool.rb +180 -0
- data/lib/riak/client/protobuffs_backend.rb +15 -5
- data/lib/riak/client/search.rb +9 -3
- data/lib/riak/link.rb +5 -7
- data/lib/riak/locale/en.yml +1 -0
- data/lib/riak/node/configuration.rb +1 -0
- data/lib/riak/node/defaults.rb +19 -6
- data/lib/riak/node/generation.rb +9 -2
- data/lib/riak/node/log.rb +2 -2
- data/lib/riak/node/version.rb +22 -16
- data/lib/riak/robject.rb +19 -3
- data/lib/riak/serializers.rb +1 -1
- data/lib/riak/test_server.rb +10 -2
- data/lib/riak/version.rb +1 -1
- data/riak-client.gemspec +3 -3
- data/spec/failover/failover.rb +59 -0
- data/spec/integration/riak/http_backends_spec.rb +2 -2
- data/spec/integration/riak/node_spec.rb +16 -24
- data/spec/integration/riak/protobuffs_backends_spec.rb +1 -1
- data/spec/integration/riak/test_server_spec.rb +4 -3
- data/spec/integration/riak/threading_spec.rb +193 -0
- data/spec/riak/beefcake_protobuffs_backend/object_methods_spec.rb +23 -0
- data/spec/riak/beefcake_protobuffs_backend_spec.rb +4 -2
- data/spec/riak/bucket_spec.rb +2 -1
- data/spec/riak/client_spec.rb +80 -181
- data/spec/riak/excon_backend_spec.rb +3 -2
- data/spec/riak/http_backend/configuration_spec.rb +37 -5
- data/spec/riak/http_backend/object_methods_spec.rb +1 -1
- data/spec/riak/http_backend/transport_methods_spec.rb +2 -2
- data/spec/riak/http_backend_spec.rb +53 -3
- data/spec/riak/map_reduce_spec.rb +1 -1
- data/spec/riak/net_http_backend_spec.rb +1 -2
- data/spec/riak/node_spec.rb +173 -0
- data/spec/riak/pool_spec.rb +306 -0
- data/spec/riak/robject_spec.rb +8 -4
- data/spec/riak/search_spec.rb +66 -15
- data/spec/riak/serializers_spec.rb +12 -1
- data/spec/spec_helper.rb +9 -1
- data/spec/support/http_backend_implementation_examples.rb +6 -2
- data/spec/support/sometimes.rb +46 -0
- data/spec/support/test_server.rb +50 -19
- data/spec/support/unified_backend_examples.rb +11 -10
- data/spec/support/version_filter.rb +14 -0
- metadata +40 -29
- data/lib/active_support/cache/riak_store.rb +0 -2
- data/lib/riak/cache_store.rb +0 -84
- data/lib/riak/client/pump.rb +0 -30
- data/lib/riak/util/fiber1.8.rb +0 -48
- data/spec/integration/riak/cache_store_spec.rb +0 -129
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Riak
|
4
|
+
class Client
|
5
|
+
# A re-entrant thread-safe resource pool. Generates new resources on
|
6
|
+
# demand.
|
7
|
+
# @private
|
8
|
+
class Pool
|
9
|
+
# Raised when a taken element should be deleted from the pool.
|
10
|
+
class BadResource < RuntimeError
|
11
|
+
end
|
12
|
+
|
13
|
+
# An element of the pool. Comprises an object with an owning
|
14
|
+
# thread.
|
15
|
+
# @private
|
16
|
+
class Element
|
17
|
+
attr_accessor :object
|
18
|
+
attr_accessor :owner
|
19
|
+
def initialize(object)
|
20
|
+
@object = object
|
21
|
+
@owner = owner
|
22
|
+
end
|
23
|
+
|
24
|
+
# Claims this element of the pool for the current Thread.
|
25
|
+
def lock
|
26
|
+
self.owner = Thread.current
|
27
|
+
end
|
28
|
+
|
29
|
+
# Is this element locked/claimed?
|
30
|
+
def locked?
|
31
|
+
not owner.nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Releases this element of the pool from the current Thread.
|
35
|
+
def unlock
|
36
|
+
self.owner = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Is this element available for use?
|
40
|
+
def unlocked?
|
41
|
+
owner.nil?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor :pool
|
46
|
+
attr_accessor :open
|
47
|
+
attr_accessor :close
|
48
|
+
|
49
|
+
# Open is a callable which returns a new object for the pool. Close is
|
50
|
+
# called with an object before it is freed.
|
51
|
+
def initialize(open, close)
|
52
|
+
@open = open
|
53
|
+
@close = close
|
54
|
+
@lock = Mutex.new
|
55
|
+
@iterator = Mutex.new
|
56
|
+
@element_released = ConditionVariable.new
|
57
|
+
@pool = Set.new
|
58
|
+
end
|
59
|
+
|
60
|
+
# On each element of the pool, calls close(element) and removes it.
|
61
|
+
# @private
|
62
|
+
def clear
|
63
|
+
each_element do |e|
|
64
|
+
delete_element e
|
65
|
+
end
|
66
|
+
end
|
67
|
+
alias :close :clear
|
68
|
+
|
69
|
+
# Deletes an element of the pool. Calls close on its object.
|
70
|
+
# Not intendend for external use.
|
71
|
+
def delete_element(e)
|
72
|
+
@close.call(e.object)
|
73
|
+
@lock.synchronize do
|
74
|
+
@pool.delete e
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Locks each element in turn and closes/deletes elements for which the
|
79
|
+
# object passes the block.
|
80
|
+
def delete_if
|
81
|
+
raise ArgumentError, "block required" unless block_given?
|
82
|
+
|
83
|
+
each_element do |e|
|
84
|
+
if yield e.object
|
85
|
+
delete_element e
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Acquire an element of the pool. Yields the object. If all
|
91
|
+
# elements are claimed, it will create another one.
|
92
|
+
# @yield [obj] a block that will perform some action with the
|
93
|
+
# element of the pool
|
94
|
+
# @yieldparam [Object] resource a resource managed by the pool.
|
95
|
+
# Locked for the duration of the block
|
96
|
+
# @param [callable] :filter a callable which receives objects and has
|
97
|
+
# the opportunity to reject each in turn.
|
98
|
+
# @param [Object] :default if no resources are available, use this object
|
99
|
+
# instead of calling #open.
|
100
|
+
# @private
|
101
|
+
def take(opts = {})
|
102
|
+
unless block_given?
|
103
|
+
raise ArgumentError, "block required"
|
104
|
+
end
|
105
|
+
|
106
|
+
r = nil
|
107
|
+
begin
|
108
|
+
e = nil
|
109
|
+
@lock.synchronize do
|
110
|
+
# Find an existing element.
|
111
|
+
if f = opts[:filter]
|
112
|
+
e = pool.find { |e| e.unlocked? and f.call(e.object) }
|
113
|
+
else
|
114
|
+
e = pool.find { |e| e.unlocked? }
|
115
|
+
end
|
116
|
+
|
117
|
+
unless e
|
118
|
+
# No objects were acceptable
|
119
|
+
resource = opts[:default] || @open.call
|
120
|
+
e = Element.new(resource)
|
121
|
+
pool << e
|
122
|
+
end
|
123
|
+
e.lock
|
124
|
+
end
|
125
|
+
|
126
|
+
r = yield e.object
|
127
|
+
rescue BadResource
|
128
|
+
delete_element e
|
129
|
+
raise
|
130
|
+
ensure
|
131
|
+
# Unlock
|
132
|
+
if e
|
133
|
+
e.unlock
|
134
|
+
@element_released.signal
|
135
|
+
end
|
136
|
+
end
|
137
|
+
r
|
138
|
+
end
|
139
|
+
alias >> take
|
140
|
+
|
141
|
+
# Iterate over a snapshot of the pool. Yielded objects are locked for the
|
142
|
+
# duration of the block. This may block the current thread until elements
|
143
|
+
# are released by other threads.
|
144
|
+
# @private
|
145
|
+
def each_element
|
146
|
+
targets = @pool.to_a
|
147
|
+
unlocked = []
|
148
|
+
|
149
|
+
@iterator.synchronize do
|
150
|
+
until targets.empty?
|
151
|
+
@lock.synchronize do
|
152
|
+
unlocked, targets = targets.partition {|e| e.unlocked? }
|
153
|
+
unlocked.each {|e| e.lock }
|
154
|
+
end
|
155
|
+
|
156
|
+
unlocked.each do |e|
|
157
|
+
begin
|
158
|
+
yield e
|
159
|
+
ensure
|
160
|
+
e.unlock
|
161
|
+
end
|
162
|
+
end
|
163
|
+
@element_released.wait(@iterator) unless targets.empty?
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# As each_element, but yields objects, not wrapper elements.
|
169
|
+
def each
|
170
|
+
each_element do |e|
|
171
|
+
yield e.object
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def size
|
176
|
+
@lock.synchronize { @pool.size }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -8,6 +8,7 @@ module Riak
|
|
8
8
|
class Client
|
9
9
|
class ProtobuffsBackend
|
10
10
|
include Util::Translation
|
11
|
+
include Util::Escape
|
11
12
|
|
12
13
|
# Message Codes Enum
|
13
14
|
MESSAGE_CODES = %W[
|
@@ -46,8 +47,10 @@ module Riak
|
|
46
47
|
end
|
47
48
|
|
48
49
|
attr_accessor :client
|
49
|
-
|
50
|
+
attr_accessor :node
|
51
|
+
def initialize(client, node)
|
50
52
|
@client = client
|
53
|
+
@node = node
|
51
54
|
end
|
52
55
|
|
53
56
|
simple :ping, :PingReq
|
@@ -67,6 +70,11 @@ module Riak
|
|
67
70
|
reduce(%w[riak_kv_mapreduce reduce_identity], :arg => {:reduce_phase_only_1 => true}, :keep => true)).map {|p| p.last }
|
68
71
|
end
|
69
72
|
|
73
|
+
# Gracefully shuts down this connection.
|
74
|
+
def teardown
|
75
|
+
reset_socket
|
76
|
+
end
|
77
|
+
|
70
78
|
private
|
71
79
|
# Implemented by subclasses
|
72
80
|
def decode_response
|
@@ -74,18 +82,20 @@ module Riak
|
|
74
82
|
end
|
75
83
|
|
76
84
|
def socket
|
77
|
-
|
85
|
+
@socket ||= new_socket
|
78
86
|
end
|
79
87
|
|
80
88
|
def new_socket
|
81
|
-
socket = TCPSocket.new(@
|
89
|
+
socket = TCPSocket.new(@node.host, @node.pb_port)
|
82
90
|
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
|
91
|
+
#TODO: Should we set the client ID here?
|
92
|
+
# set_client_id @client.client_id
|
83
93
|
socket
|
84
94
|
end
|
85
95
|
|
86
96
|
def reset_socket
|
87
|
-
socket.close if
|
88
|
-
|
97
|
+
@socket.close if @socket && !@socket.closed?
|
98
|
+
@socket = nil
|
89
99
|
end
|
90
100
|
|
91
101
|
UINTMAX = 0xffffffff
|
data/lib/riak/client/search.rb
CHANGED
@@ -20,7 +20,9 @@ module Riak
|
|
20
20
|
def search(*args)
|
21
21
|
options = args.extract_options!
|
22
22
|
index, query = args[-2], args[-1] # Allows nil index, while keeping it as firstargument
|
23
|
-
http
|
23
|
+
http do |h|
|
24
|
+
h.search(index, query, options)
|
25
|
+
end
|
24
26
|
end
|
25
27
|
alias :select :search
|
26
28
|
|
@@ -46,7 +48,9 @@ module Riak
|
|
46
48
|
end
|
47
49
|
end
|
48
50
|
end
|
49
|
-
http
|
51
|
+
http do |h|
|
52
|
+
h.update_search_index(index, xml.target!)
|
53
|
+
end
|
50
54
|
true
|
51
55
|
end
|
52
56
|
alias :add_doc :index
|
@@ -76,7 +80,9 @@ module Riak
|
|
76
80
|
end
|
77
81
|
end
|
78
82
|
end
|
79
|
-
http
|
83
|
+
http do |h|
|
84
|
+
h.update_search_index(index, xml.target!)
|
85
|
+
end
|
80
86
|
true
|
81
87
|
end
|
82
88
|
alias :delete_doc :remove
|
data/lib/riak/link.rb
CHANGED
@@ -53,14 +53,12 @@ module Riak
|
|
53
53
|
|
54
54
|
# @return [String] the URL (relative or absolute) of the related resource
|
55
55
|
def url(new_scheme=false)
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
"/riak/#{escape(bucket)}" + (key.blank? ? "" : "/#{escape(key)}")
|
61
|
-
end
|
56
|
+
return @url unless @bucket
|
57
|
+
|
58
|
+
if new_scheme
|
59
|
+
"/buckets/#{escape(bucket)}" + (key.blank? ? "" : "/keys/#{escape(key)}")
|
62
60
|
else
|
63
|
-
|
61
|
+
"/riak/#{escape(bucket)}" + (key.blank? ? "" : "/#{escape(key)}")
|
64
62
|
end
|
65
63
|
end
|
66
64
|
|
data/lib/riak/locale/en.yml
CHANGED
@@ -30,6 +30,7 @@ en:
|
|
30
30
|
loading_bucket: "while loading bucket '%{name}'"
|
31
31
|
list_buckets: "Riak::Client#buckets is an expensive operation that should not be used in production.\n %{backtrace}"
|
32
32
|
list_keys: "Riak::Bucket#keys is an expensive operation that should not be used in production.\n %{backtrace}"
|
33
|
+
luwak_unsupported: "Riak server does not support Luwak. Enable it in app.config before using."
|
33
34
|
missing_block: "A block must be given."
|
34
35
|
missing_host_and_port: "You must specify a host and port, or use the defaults of 127.0.0.1:8098"
|
35
36
|
module_function_pair_required: "function must have two elements when an array"
|
@@ -156,6 +156,7 @@ module Riak
|
|
156
156
|
env[:eleveldb][:data_root] ||= (data + 'leveldb').expand_path.to_s
|
157
157
|
env[:merge_index][:data_root] ||= (data + 'merge_index').expand_path.to_s
|
158
158
|
env[:riak_core][:ring_state_dir] ||= ring.expand_path.to_s
|
159
|
+
env[:riak_core][:slide_private_dir] ||= (data + 'slide-data').expand_path.to_s
|
159
160
|
NODE_DIRECTORIES.each do |dir|
|
160
161
|
next if [:ring, :pipe].include?(dir)
|
161
162
|
env[:riak_core][:"platform_#{dir}_dir"] ||= send(dir).to_s
|
data/lib/riak/node/defaults.rb
CHANGED
@@ -2,7 +2,7 @@ require 'riak/core_ext/deep_dup'
|
|
2
2
|
|
3
3
|
module Riak
|
4
4
|
class Node
|
5
|
-
# Settings based on Riak
|
5
|
+
# Settings based on Riak 1.1.
|
6
6
|
ENV_DEFAULTS = {
|
7
7
|
:riak_core => {
|
8
8
|
:ring_creation_size => 64
|
@@ -16,11 +16,13 @@ module Riak
|
|
16
16
|
:js_max_vm_mem => 8,
|
17
17
|
:js_thread_stack => 16,
|
18
18
|
:riak_kv_stat => true,
|
19
|
-
:legacy_stats =>
|
19
|
+
:legacy_stats => true,
|
20
20
|
:vnode_vclocks => true,
|
21
21
|
:http_url_encoding => :on,
|
22
22
|
:legacy_keylisting => false,
|
23
23
|
:mapred_system => :pipe,
|
24
|
+
:mapred_2i_pipe => true,
|
25
|
+
:listkeys_backpressure => true,
|
24
26
|
:add_paths => []
|
25
27
|
},
|
26
28
|
:riak_search => {
|
@@ -36,21 +38,32 @@ module Riak
|
|
36
38
|
:eleveldb => {},
|
37
39
|
:bitcask => {},
|
38
40
|
:lager => {
|
39
|
-
:crash_log_size =>
|
41
|
+
:crash_log_size => 10485760,
|
42
|
+
:crash_log_msg_size => 65536,
|
43
|
+
:crash_log_date => "$D0",
|
44
|
+
:crash_log_count => 5,
|
40
45
|
:error_logger_redirect => true
|
41
46
|
},
|
42
47
|
:riak_sysmon => {
|
43
48
|
:process_limit => 30,
|
44
49
|
:port_limit => 30,
|
45
|
-
:gc_ms_limit =>
|
46
|
-
:heap_word_limit =>
|
50
|
+
:gc_ms_limit => 100,
|
51
|
+
:heap_word_limit => 40111000,
|
52
|
+
:busy_port => true,
|
53
|
+
:busy_dist_port => true
|
47
54
|
},
|
48
55
|
:sasl => {
|
49
56
|
:sasl_error_logger => false
|
57
|
+
},
|
58
|
+
:riak_control => {
|
59
|
+
:enabled => false,
|
60
|
+
:auth => :userlist,
|
61
|
+
:userlist => {"user" => "pass"},
|
62
|
+
:admin => true
|
50
63
|
}
|
51
64
|
}.freeze
|
52
65
|
|
53
|
-
# Based on Riak
|
66
|
+
# Based on Riak 1.1.
|
54
67
|
VM_DEFAULTS = {
|
55
68
|
"+K" => true,
|
56
69
|
"+A" => 64,
|
data/lib/riak/node/generation.rb
CHANGED
@@ -29,7 +29,13 @@ module Riak
|
|
29
29
|
def drop
|
30
30
|
was_started = started?
|
31
31
|
stop if was_started
|
32
|
-
data.children.each
|
32
|
+
data.children.each do |item|
|
33
|
+
if item.directory?
|
34
|
+
item.children.each {|c| c.rmtree }
|
35
|
+
else
|
36
|
+
item.delete
|
37
|
+
end
|
38
|
+
end
|
33
39
|
start if was_started
|
34
40
|
end
|
35
41
|
|
@@ -76,7 +82,8 @@ module Riak
|
|
76
82
|
line.sub!(/(RUNNER_ETC_DIR=)(.*)/, '\1' + etc.to_s)
|
77
83
|
line.sub!(/(RUNNER_USER=)(.*)/, '\1')
|
78
84
|
line.sub!(/(RUNNER_LOG_DIR=)(.*)/, '\1' + log.to_s)
|
79
|
-
line.sub!(/(PIPE_DIR=)(.*)/, '\1' + pipe.to_s)
|
85
|
+
line.sub!(/(PIPE_DIR=)(.*)/, '\1' + pipe.to_s + "/") # PIPE_DIR must have a trailing slash
|
86
|
+
line.sub!(/(PLATFORM_DATA_DIR=)(.*)/, '\1' + data.to_s)
|
80
87
|
line.sub!('grep "$RUNNER_BASE_DIR/.*/[b]eam"', 'grep "$RUNNER_ETC_DIR/app.config"')
|
81
88
|
if line.strip == "RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*}"
|
82
89
|
line = "RUNNER_BASE_DIR=#{source.parent.to_s}\n"
|
data/lib/riak/node/log.rb
CHANGED
@@ -23,8 +23,8 @@ module Riak
|
|
23
23
|
def expand_log_level(level)
|
24
24
|
case level
|
25
25
|
when Range
|
26
|
-
first = LAGER_LEVELS.index(level.begin) || 0
|
27
|
-
last = LAGER_LEVELS.index(level.end) || -1
|
26
|
+
first = LAGER_LEVELS.index(level.begin.to_sym) || 0
|
27
|
+
last = LAGER_LEVELS.index(level.end.to_sym) || -1
|
28
28
|
LAGER_LEVELS[first..last]
|
29
29
|
when Symbol
|
30
30
|
level
|
data/lib/riak/node/version.rb
CHANGED
@@ -2,10 +2,18 @@ require 'pathname'
|
|
2
2
|
|
3
3
|
module Riak
|
4
4
|
class Node
|
5
|
+
# @return [String] the version of the Riak node
|
5
6
|
def version
|
6
7
|
@version ||= configure_version
|
7
8
|
end
|
8
9
|
|
10
|
+
# @return [Pathname] the location of Riak installation, aka RUNNER_BASE_DIR
|
11
|
+
def base_dir
|
12
|
+
@base_dir ||= configure_base_dir
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
# Detects the Riak version from the generated release
|
9
17
|
def configure_version
|
10
18
|
if base_dir
|
11
19
|
versions = (base_dir + 'releases' + 'start_erl.data').read
|
@@ -13,24 +21,22 @@ module Riak
|
|
13
21
|
end
|
14
22
|
end
|
15
23
|
|
16
|
-
|
17
|
-
@base_dir ||= configure_base_dir
|
18
|
-
end
|
19
|
-
|
24
|
+
# Determines the base_dir from source control script
|
20
25
|
def configure_base_dir
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
26
|
+
# Use the script from the source directory so we don't require
|
27
|
+
# it to be generated first.
|
28
|
+
(source + control_script_name).each_line.find {|l| l =~ /^RUNNER_BASE_DIR=(.*)/ }
|
29
|
+
|
30
|
+
# There should only be one matching line, so the contents of $1
|
31
|
+
# will be the matched path. If there's nothing matched, we
|
32
|
+
# return nil.
|
33
|
+
case $1
|
34
|
+
when '${RUNNER_SCRIPT_DIR%/*}'
|
35
|
+
source.parent
|
36
|
+
when String
|
37
|
+
Pathname.new($1).expand_path
|
25
38
|
else
|
26
|
-
|
27
|
-
case line
|
28
|
-
when /^RUNNER_BASE_DIR=$\{RUNNER_SCRIPT_DIR%\/*\}/
|
29
|
-
source.parent
|
30
|
-
else
|
31
|
-
path = pattern.match(line)[1]
|
32
|
-
Pathname.new(path).expand_path if File.directory?(path)
|
33
|
-
end
|
39
|
+
nil
|
34
40
|
end
|
35
41
|
end
|
36
42
|
end
|