better-riak-client 1.0.5
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.
- data/LICENSE +16 -0
- data/README.markdown +198 -0
- data/RELEASE_NOTES.md +211 -0
- data/better-riak-client.gemspec +61 -0
- 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 +697 -0
- data/erl_src/riak_search_test_backend.beam +0 -0
- data/erl_src/riak_search_test_backend.erl +175 -0
- data/lib/riak/bucket.rb +221 -0
- data/lib/riak/client/beefcake/messages.rb +213 -0
- data/lib/riak/client/beefcake/object_methods.rb +111 -0
- data/lib/riak/client/beefcake_protobuffs_backend.rb +226 -0
- data/lib/riak/client/decaying.rb +36 -0
- data/lib/riak/client/excon_backend.rb +162 -0
- data/lib/riak/client/feature_detection.rb +88 -0
- data/lib/riak/client/http_backend/configuration.rb +211 -0
- data/lib/riak/client/http_backend/key_streamer.rb +43 -0
- data/lib/riak/client/http_backend/object_methods.rb +106 -0
- data/lib/riak/client/http_backend/request_headers.rb +34 -0
- data/lib/riak/client/http_backend/transport_methods.rb +201 -0
- data/lib/riak/client/http_backend.rb +340 -0
- data/lib/riak/client/net_http_backend.rb +82 -0
- data/lib/riak/client/node.rb +115 -0
- data/lib/riak/client/protobuffs_backend.rb +173 -0
- data/lib/riak/client/search.rb +91 -0
- data/lib/riak/client.rb +540 -0
- data/lib/riak/cluster.rb +151 -0
- data/lib/riak/core_ext/blank.rb +53 -0
- data/lib/riak/core_ext/deep_dup.rb +13 -0
- data/lib/riak/core_ext/extract_options.rb +7 -0
- data/lib/riak/core_ext/json.rb +15 -0
- data/lib/riak/core_ext/slice.rb +18 -0
- data/lib/riak/core_ext/stringify_keys.rb +10 -0
- data/lib/riak/core_ext/symbolize_keys.rb +10 -0
- data/lib/riak/core_ext/to_param.rb +31 -0
- data/lib/riak/core_ext.rb +7 -0
- data/lib/riak/encoding.rb +6 -0
- data/lib/riak/failed_request.rb +81 -0
- data/lib/riak/i18n.rb +5 -0
- data/lib/riak/json.rb +52 -0
- data/lib/riak/link.rb +94 -0
- data/lib/riak/locale/en.yml +53 -0
- data/lib/riak/locale/fr.yml +52 -0
- data/lib/riak/map_reduce/filter_builder.rb +103 -0
- data/lib/riak/map_reduce/phase.rb +98 -0
- data/lib/riak/map_reduce.rb +225 -0
- data/lib/riak/map_reduce_error.rb +7 -0
- data/lib/riak/node/configuration.rb +293 -0
- data/lib/riak/node/console.rb +133 -0
- data/lib/riak/node/control.rb +207 -0
- data/lib/riak/node/defaults.rb +83 -0
- data/lib/riak/node/generation.rb +106 -0
- data/lib/riak/node/log.rb +34 -0
- data/lib/riak/node/version.rb +43 -0
- data/lib/riak/node.rb +38 -0
- data/lib/riak/robject.rb +318 -0
- data/lib/riak/search.rb +3 -0
- data/lib/riak/serializers.rb +74 -0
- data/lib/riak/stamp.rb +77 -0
- data/lib/riak/test_server.rb +89 -0
- data/lib/riak/util/escape.rb +76 -0
- data/lib/riak/util/headers.rb +53 -0
- data/lib/riak/util/multipart/stream_parser.rb +62 -0
- data/lib/riak/util/multipart.rb +52 -0
- data/lib/riak/util/tcp_socket_extensions.rb +58 -0
- data/lib/riak/util/translation.rb +19 -0
- data/lib/riak/version.rb +3 -0
- data/lib/riak/walk_spec.rb +105 -0
- data/lib/riak.rb +21 -0
- metadata +348 -0
@@ -0,0 +1,293 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Riak
|
5
|
+
class Node
|
6
|
+
# The directories (and accessor methods) that will be created
|
7
|
+
# under the generated node.
|
8
|
+
NODE_DIRECTORIES = [:bin, :etc, :log, :data, :ring, :pipe]
|
9
|
+
|
10
|
+
NODE_DIRECTORIES.each do |dir|
|
11
|
+
# Makes accessor methods for all the node directories that
|
12
|
+
# return Pathname objects.
|
13
|
+
class_eval %Q{
|
14
|
+
def #{dir}
|
15
|
+
root + '#{dir}'
|
16
|
+
end
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Hash] the contents of the Erlang environment, which will
|
21
|
+
# be created into the app.config file.
|
22
|
+
attr_reader :env
|
23
|
+
|
24
|
+
# @return [Hash] the command-line switches for the Erlang virtual
|
25
|
+
# machine, which will be created into the vm.args file
|
26
|
+
attr_reader :vm
|
27
|
+
|
28
|
+
# @return [Hash] the configuration that was passed to the Node
|
29
|
+
# when initialized
|
30
|
+
attr_reader :configuration
|
31
|
+
|
32
|
+
# @return [Array<Pathname>] where user Erlang code will be loaded from
|
33
|
+
def erlang_sources
|
34
|
+
env[:riak_kv][:add_paths].map {|p| Pathname.new(p) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Pathname] where user Javascript code will be loaded from
|
38
|
+
def javascript_source
|
39
|
+
Pathname.new(env[:riak_kv][:js_source_dir])
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Fixnum] the size of the ring, i.e. number of partitions
|
43
|
+
def ring_size
|
44
|
+
env[:riak_core][:ring_creation_size]
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Fixnum] The port used for handing off data to other nodes.
|
48
|
+
def handoff_port
|
49
|
+
env[:riak_core][:handoff_port]
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Fixnum] The port to which the HTTP API is connected.
|
53
|
+
def http_port
|
54
|
+
# We'll only support 0.14 and later, which uses http rather than web_ip/web_port
|
55
|
+
env[:riak_core][:http][0][1]
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Fixnum] the port to which the Protocol Buffers API is connected.
|
59
|
+
def pb_port
|
60
|
+
env[:riak_kv][:pb_port]
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [String] the interface to which the HTTP API is connected
|
64
|
+
def http_ip
|
65
|
+
env[:riak_core][:http][0][0]
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [String] the interface to which the Protocol Buffers API is connected
|
69
|
+
def pb_ip
|
70
|
+
env[:riak_kv][:pb_ip]
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Symbol] the storage backend for Riak Search.
|
74
|
+
def search_backend
|
75
|
+
env[:riak_search][:search_backend]
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Symbol] the storage backend for Riak KV.
|
79
|
+
def kv_backend
|
80
|
+
env[:riak_kv][:storage_backend]
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [String] the name of the Riak node as seen by distributed Erlang
|
84
|
+
# communication. AKA "-name" in vm.args.
|
85
|
+
def name
|
86
|
+
vm['-name']
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [String] the cookie/shared secret used for connecting
|
90
|
+
# a cluster
|
91
|
+
def cookie
|
92
|
+
vm['-setcookie']
|
93
|
+
end
|
94
|
+
|
95
|
+
# The source of the Riak installation from where the {Node} will
|
96
|
+
# be generated. This should point to the directory that contains
|
97
|
+
# the 'riak[search]' and 'riak[search]-admin' scripts.
|
98
|
+
# @return [Pathname] the source Riak installation
|
99
|
+
attr_reader :source
|
100
|
+
|
101
|
+
# The root directory of the {Node}, where all files are placed
|
102
|
+
# after generation.
|
103
|
+
# @return [Pathname] the root directory of the node
|
104
|
+
attr_reader :root
|
105
|
+
|
106
|
+
# The script for starting, stopping and pinging the Node.
|
107
|
+
# @return [Pathname] the path to the control script
|
108
|
+
def control_script
|
109
|
+
@control_script ||= root + 'bin' + control_script_name
|
110
|
+
end
|
111
|
+
|
112
|
+
# The name of the 'riak' or 'riaksearch' control script.
|
113
|
+
# @return [String] 'riak' or 'riaksearch'
|
114
|
+
def control_script_name
|
115
|
+
@control_script_name ||= (source + 'riaksearch').exist? ? 'riaksearch' : 'riak'
|
116
|
+
end
|
117
|
+
|
118
|
+
# The script for controlling non-lifecycle features of Riak like
|
119
|
+
# joining, leaving, status, ringready, etc.
|
120
|
+
# @return [Pathname] the path to the administrative script
|
121
|
+
def admin_script
|
122
|
+
@admin_script ||= root + 'bin' + "#{control_script_name}-admin"
|
123
|
+
end
|
124
|
+
|
125
|
+
# The "manifest" file where the node configuration will be
|
126
|
+
# written.
|
127
|
+
# @return [Pathname] the path to the manifest
|
128
|
+
def manifest
|
129
|
+
root + '.node.yml'
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
# Populates the proper node configuration from the input config.
|
134
|
+
def configure(hash)
|
135
|
+
raise ArgumentError, t('source_and_root_required') unless hash[:source] && hash[:root]
|
136
|
+
@configuration = hash
|
137
|
+
configure_paths
|
138
|
+
configure_manifest
|
139
|
+
configure_settings
|
140
|
+
configure_logging
|
141
|
+
configure_data
|
142
|
+
configure_ports(hash[:interface], hash[:min_port])
|
143
|
+
configure_name(hash[:interface])
|
144
|
+
end
|
145
|
+
|
146
|
+
# Reads the manifest if it exists, overrides the passed configuration.
|
147
|
+
def configure_manifest
|
148
|
+
@configuration = YAML.load_file(manifest.to_s) if exist?
|
149
|
+
end
|
150
|
+
|
151
|
+
# Sets the data directories for the various on-disk backends and
|
152
|
+
# the ring state.
|
153
|
+
def configure_data
|
154
|
+
[:bitcask, :eleveldb, :merge_index].each {|k| env[k] ||= {} }
|
155
|
+
env[:bitcask][:data_root] ||= (data + 'bitcask').expand_path.to_s
|
156
|
+
env[:eleveldb][:data_root] ||= (data + 'leveldb').expand_path.to_s
|
157
|
+
env[:merge_index][:data_root] ||= (data + 'merge_index').expand_path.to_s
|
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
|
160
|
+
NODE_DIRECTORIES.each do |dir|
|
161
|
+
next if [:ring, :pipe].include?(dir)
|
162
|
+
env[:riak_core][:"platform_#{dir}_dir"] ||= send(dir).to_s
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Sets directories and handlers for logging.
|
167
|
+
def configure_logging
|
168
|
+
if env[:lager]
|
169
|
+
env[:lager][:handlers] = {
|
170
|
+
:lager_console_backend => :info,
|
171
|
+
:lager_file_backend => [
|
172
|
+
Tuple[(log+"error.log").expand_path.to_s, :error],
|
173
|
+
Tuple[(log+"console.log").expand_path.to_s, :info]
|
174
|
+
]
|
175
|
+
}
|
176
|
+
env[:lager][:crash_log] = (log+"crash.log").to_s
|
177
|
+
else
|
178
|
+
# TODO: Need a better way to detect this, the defaults point
|
179
|
+
# to 1.0-style configs. Maybe there should be some kind of
|
180
|
+
# detection routine.
|
181
|
+
# Use sasl error logger for 0.14.
|
182
|
+
env[:riak_err] ||= {
|
183
|
+
:term_max_size => 65536,
|
184
|
+
:fmt_max_bytes => 65536
|
185
|
+
}
|
186
|
+
env[:sasl] = {
|
187
|
+
:sasl_error_logger => Tuple[:file, (log+"sasl-error.log").expand_path.to_s],
|
188
|
+
:errlog_type => :error,
|
189
|
+
:error_logger_mf_dir => (log+"sasl").expand_path.to_s,
|
190
|
+
:error_logger_mf_maxbytes => 10485760,
|
191
|
+
:error_logger_mf_maxfiles => 5
|
192
|
+
}
|
193
|
+
end
|
194
|
+
vm['-env ERL_CRASH_DUMP'] = (log + 'erl_crash.dump').to_s
|
195
|
+
end
|
196
|
+
|
197
|
+
# Sets the node name and cookie for distributed Erlang.
|
198
|
+
def configure_name(interface)
|
199
|
+
interface ||= "127.0.0.1"
|
200
|
+
vm["-name"] ||= configuration[:name] || "riak#{rand(1000000).to_s}@#{interface}"
|
201
|
+
vm["-setcookie"] ||= configuration[:cookie] || "#{rand(100000).to_s}_#{rand(1000000).to_s}"
|
202
|
+
end
|
203
|
+
|
204
|
+
# Merges input configuration with the defaults.
|
205
|
+
def configure_settings
|
206
|
+
@env = deep_merge(env.dup, configuration[:env]) if configuration[:env]
|
207
|
+
@vm = vm.merge(configuration[:vm]) if configuration[:vm]
|
208
|
+
end
|
209
|
+
|
210
|
+
# Sets the source directory and root directory of the generated node.
|
211
|
+
def configure_paths
|
212
|
+
@source = Pathname.new(configuration[:source]).expand_path
|
213
|
+
@root = Pathname.new(configuration[:root]).expand_path
|
214
|
+
# Systems like Homebrew and Stow symlink the scripts into $PATH,
|
215
|
+
# but RUNNER_BASE_DIR is not relative to the symlink.
|
216
|
+
if (@source + control_script_name).symlink?
|
217
|
+
@source = (@source + control_script_name).realpath.parent
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Sets ports and interfaces for http, protocol buffers, and handoff.
|
222
|
+
def configure_ports(interface, min_port)
|
223
|
+
interface ||= "127.0.0.1"
|
224
|
+
min_port ||= 8080
|
225
|
+
unless env[:riak_core][:http]
|
226
|
+
env[:riak_core][:http] = [Tuple[interface, min_port]]
|
227
|
+
min_port += 1
|
228
|
+
end
|
229
|
+
env[:riak_core][:http] = env[:riak_core][:http].map {|pair| Tuple[*pair] }
|
230
|
+
env[:riak_kv][:pb_ip] = interface unless env[:riak_kv][:pb_ip]
|
231
|
+
unless env[:riak_kv][:pb_port]
|
232
|
+
env[:riak_kv][:pb_port] = min_port
|
233
|
+
min_port += 1
|
234
|
+
end
|
235
|
+
unless env[:riak_core][:handoff_port]
|
236
|
+
env[:riak_core][:handoff_port] = min_port
|
237
|
+
min_port += 1
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# Implements a deep-merge of two {Hash} instances.
|
242
|
+
# @param [Hash] source the original hash
|
243
|
+
# @param [Hash] target the new hash
|
244
|
+
# @return [Hash] a {Hash} whose {Hash} values have also been merged
|
245
|
+
def deep_merge(source, target)
|
246
|
+
source.merge(target) do |key, old_val, new_val|
|
247
|
+
if Hash === old_val && Hash === new_val
|
248
|
+
deep_merge(old_val, new_val)
|
249
|
+
else
|
250
|
+
new_val
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# This class lets us specify that some settings should be emitted
|
256
|
+
# as Erlang tuples, even though the first element is not
|
257
|
+
# necessarily a Symbol.
|
258
|
+
class Tuple < Array; end
|
259
|
+
|
260
|
+
# Recursively converts a {Hash} into an Erlang configuration
|
261
|
+
# string that is appropriate for the app.config file.
|
262
|
+
# @param [Hash] hash a collection of configuration values
|
263
|
+
# @param [Fixnum] depth the current nesting level of
|
264
|
+
# generation/indentation
|
265
|
+
# @return [String] Erlang proplists in a String for use in
|
266
|
+
# app.config
|
267
|
+
def to_erlang_config(hash, depth = 1)
|
268
|
+
padding = ' ' * depth
|
269
|
+
parent_padding = ' ' * (depth-1)
|
270
|
+
values = hash.map do |k,v|
|
271
|
+
"{#{k}, #{value_to_erlang(v, depth)}}"
|
272
|
+
end.join(",\n#{padding}")
|
273
|
+
"[\n#{padding}#{values}\n#{parent_padding}]"
|
274
|
+
end
|
275
|
+
|
276
|
+
# Converts a value to an Erlang term. Mutually recursive with
|
277
|
+
# {#to_erlang_config}.
|
278
|
+
def value_to_erlang(v, depth=1)
|
279
|
+
case v
|
280
|
+
when Hash
|
281
|
+
to_erlang_config(v, depth+1)
|
282
|
+
when String
|
283
|
+
"\"#{v}\""
|
284
|
+
when Tuple
|
285
|
+
"{" << v.map {|i| value_to_erlang(i, depth+1) }.join(", ") << "}"
|
286
|
+
when Array
|
287
|
+
"[" << v.map {|i| value_to_erlang(i, depth+1) }.join(", ") << "]"
|
288
|
+
else
|
289
|
+
v.to_s
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'expect'
|
2
|
+
require 'pathname'
|
3
|
+
require 'riak/util/translation'
|
4
|
+
|
5
|
+
if ENV['DEBUG_RIAK_CONSOLE']
|
6
|
+
$expect_verbose = true
|
7
|
+
end
|
8
|
+
|
9
|
+
module Riak
|
10
|
+
class Node
|
11
|
+
# Eases working with the Erlang console when attached to the Riak
|
12
|
+
# node.
|
13
|
+
class Console
|
14
|
+
include Util::Translation
|
15
|
+
|
16
|
+
# @return [String] the name of the connected node
|
17
|
+
attr_accessor :nodename
|
18
|
+
|
19
|
+
# Opens a {Console} by connecting to the node.
|
20
|
+
# @return [Console] the opened console
|
21
|
+
def self.open(node)
|
22
|
+
new node.pipe, node.name
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates a {Console} from the IO pipes connected to the node.
|
26
|
+
# @param [String,Pathname] pipedir path to the pipes opened by
|
27
|
+
# run_erl
|
28
|
+
# @param [String] nodename the name of the node the Console will
|
29
|
+
# be attached to
|
30
|
+
def initialize(pipedir, nodename)
|
31
|
+
@nodename = nodename
|
32
|
+
@mutex = Mutex.new
|
33
|
+
@prompt = /\(#{Regexp.escape(nodename)}\)\d+>\s*/
|
34
|
+
pipedir = Pathname(pipedir)
|
35
|
+
pipedir.children.each do |path|
|
36
|
+
if path.pipe?
|
37
|
+
if path.fnmatch("*.r") # Read pipe
|
38
|
+
# debug "Found read pipe: #{path}"
|
39
|
+
@rfile ||= path
|
40
|
+
elsif path.fnmatch("*.w") # Write pipe
|
41
|
+
# debug "Found write pipe: #{path}"
|
42
|
+
@wfile ||= path
|
43
|
+
end
|
44
|
+
else
|
45
|
+
debug "Non-pipe found! #{path}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
raise ArgumentError, t('no_pipes', :path => pipedir.to_s) if [@rfile, @wfile].any? {|p| p.nil? }
|
49
|
+
|
50
|
+
begin
|
51
|
+
debug "Opening write pipe."
|
52
|
+
@w = open_write_pipe
|
53
|
+
@w.sync = true
|
54
|
+
read_ready = false
|
55
|
+
# Using threads, we get around JRuby's blocking-open
|
56
|
+
# behavior. The main thread pumps the write pipe full of
|
57
|
+
# data so that run_erl will start echoing it into the read
|
58
|
+
# pipe once we have started the open. On non-JVM rubies,
|
59
|
+
# O_NONBLOCK works and we proceed as expected.
|
60
|
+
Thread.new do
|
61
|
+
debug "Opening read pipe."
|
62
|
+
@r = open_read_pipe
|
63
|
+
read_ready = true
|
64
|
+
end
|
65
|
+
Thread.pass
|
66
|
+
@w.print "\n" until read_ready
|
67
|
+
command "ok."
|
68
|
+
debug "Initialized console: #{@r.inspect} #{@w.inspect}"
|
69
|
+
rescue => e
|
70
|
+
debug e.message
|
71
|
+
close
|
72
|
+
raise t('no_pipes', :path => pipedir.to_s) + "[ #{e.message} ]"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Sends an Erlang command to the console
|
77
|
+
# @param [String] cmd an Erlang expression to send to the node
|
78
|
+
def command(cmd)
|
79
|
+
@mutex.synchronize do
|
80
|
+
begin
|
81
|
+
debug "Sending command #{cmd.inspect}"
|
82
|
+
@w.print "#{cmd}\n"
|
83
|
+
wait_for_erlang_prompt
|
84
|
+
rescue SystemCallError
|
85
|
+
close
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Detects whether the console connection is still open, that is,
|
91
|
+
# if the node hasn't disconnected from the other side of the
|
92
|
+
# pipe.
|
93
|
+
def open?
|
94
|
+
(@r && !@r.closed?) && (@w && !@w.closed?)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Scans the output of the console until an Erlang shell prompt
|
98
|
+
# is found. Called by {#command} to ensure that the submitted
|
99
|
+
# command succeeds.
|
100
|
+
def wait_for_erlang_prompt
|
101
|
+
wait_for @prompt
|
102
|
+
end
|
103
|
+
|
104
|
+
# Scans the output of the console for the given pattern.
|
105
|
+
# @param [String, Regexp] pattern the pattern to scan for
|
106
|
+
def wait_for(pattern)
|
107
|
+
debug "Scanning for #{pattern.inspect}"
|
108
|
+
@r.expect(pattern)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Closes the console by detaching from the pipes.
|
112
|
+
def close
|
113
|
+
@r.close if @r && !@r.closed?
|
114
|
+
@w.close if @w && !@w.closed?
|
115
|
+
freeze
|
116
|
+
end
|
117
|
+
|
118
|
+
protected
|
119
|
+
|
120
|
+
def debug(msg)
|
121
|
+
$stderr.puts msg if ENV["DEBUG_RIAK_CONSOLE"]
|
122
|
+
end
|
123
|
+
|
124
|
+
def open_write_pipe
|
125
|
+
@wfile.open(File::WRONLY|File::NONBLOCK)
|
126
|
+
end
|
127
|
+
|
128
|
+
def open_read_pipe
|
129
|
+
@rfile.open(File::RDONLY|File::NONBLOCK)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'riak/node/console'
|
2
|
+
require 'riak/util/tcp_socket_extensions'
|
3
|
+
|
4
|
+
module Riak
|
5
|
+
class Node
|
6
|
+
# Regexp for parsing riak-admin status output. Takes into account
|
7
|
+
# the minor bug fixed by {https://github.com/basho/riak_kv/pull/134}
|
8
|
+
# and multiline output used when lists of things grow long.
|
9
|
+
STATS_REGEXP = /^([^:\n]+)\s:\s((?:.*)(?:\n\s+[^\n]+)*)/
|
10
|
+
|
11
|
+
# Is the node running?
|
12
|
+
# @return [true, false] If the node is running
|
13
|
+
def started?
|
14
|
+
pinged = ping
|
15
|
+
pinged.strip =~ /pong/ || pinged.strip !~ /Node '[^']+' not responding to pings/
|
16
|
+
end
|
17
|
+
|
18
|
+
# Is the node stopped? (opposite of {#started?}).
|
19
|
+
# @return [true, false] If the node is stopped
|
20
|
+
# @see #started?
|
21
|
+
def stopped?
|
22
|
+
!started?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Starts the node.
|
26
|
+
# @return [String] the output of the 'riak start' command
|
27
|
+
def start
|
28
|
+
res = riak 'start'
|
29
|
+
wait_for_startup
|
30
|
+
res
|
31
|
+
end
|
32
|
+
|
33
|
+
# Stops the node
|
34
|
+
# @return [String] the output of the 'riak stop' command
|
35
|
+
def stop
|
36
|
+
res = riak 'stop'
|
37
|
+
wait_for_shutdown
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
# Restarts the node
|
42
|
+
# @return [String] the output of the 'riak restart' command
|
43
|
+
def restart
|
44
|
+
riak 'restart'
|
45
|
+
end
|
46
|
+
|
47
|
+
# Reboots the node
|
48
|
+
# @return [String] the output of the 'riak reboot' command
|
49
|
+
def restart
|
50
|
+
riak 'reboot'
|
51
|
+
end
|
52
|
+
|
53
|
+
# Pings the node
|
54
|
+
# @return [String] the output of the 'riak ping' command
|
55
|
+
def ping
|
56
|
+
begin
|
57
|
+
riak 'ping'
|
58
|
+
rescue SystemCallError
|
59
|
+
# If the control script doesn't exist or has the wrong
|
60
|
+
# permissions, we should still return something sane so we can
|
61
|
+
# do the right thing.
|
62
|
+
"Node '#{name}' not responding to pings."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Attach to the node's console via the pipe.
|
67
|
+
# @return [Riak::Node::Console] A console manager for sending
|
68
|
+
# commands to the Riak node
|
69
|
+
# @see #with_console
|
70
|
+
def attach
|
71
|
+
Console.open self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Execute the block against the Riak node's console.
|
75
|
+
# @yield [console] A block of commands to be run against the console
|
76
|
+
# @yieldparam [Riak::Node::Console] console A console manager for
|
77
|
+
# sending commands to the Riak node
|
78
|
+
def with_console
|
79
|
+
begin
|
80
|
+
console = attach
|
81
|
+
yield console
|
82
|
+
ensure
|
83
|
+
console.close if console
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Joins the node to another node to create a cluster.
|
88
|
+
# @return [String] the output of the 'riak-admin join' command
|
89
|
+
def join(node)
|
90
|
+
node = node.name if Node === node
|
91
|
+
riak_admin 'join', node
|
92
|
+
end
|
93
|
+
|
94
|
+
# Removes this node from its current cluster, handing off all data.
|
95
|
+
# @return [String] the output of the 'riak-admin leave' command
|
96
|
+
def leave
|
97
|
+
riak_admin 'leave'
|
98
|
+
end
|
99
|
+
|
100
|
+
# Forcibly removes a node from the current cluster without
|
101
|
+
# invoking handoff.
|
102
|
+
# @return [String] the output of the 'riak-admin remove <node>'
|
103
|
+
# command
|
104
|
+
def remove(node)
|
105
|
+
node = node.name if Node === node
|
106
|
+
riak_admin 'remove', node
|
107
|
+
end
|
108
|
+
|
109
|
+
# Captures the status of the node.
|
110
|
+
# @return [Hash] a collection of information about the node
|
111
|
+
def status
|
112
|
+
output = riak_admin 'status'
|
113
|
+
if $?.success?
|
114
|
+
result = {}
|
115
|
+
Hash[output.scan(STATS_REGEXP)]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Detects whether the node's cluster has converged on the ring.
|
120
|
+
# @return [true,false] whether the ring is stable
|
121
|
+
def ringready?
|
122
|
+
output = riak_admin 'ringready'
|
123
|
+
output =~ /^TRUE/ || $?.success?
|
124
|
+
end
|
125
|
+
|
126
|
+
# Lists riak_core applications that have registered as available,
|
127
|
+
# e.g. ["riak_kv", "riak_search", "riak_pipe"]
|
128
|
+
# @return [Array<String>] a list of running services
|
129
|
+
def services
|
130
|
+
output = riak_admin 'services'
|
131
|
+
if $?.success?
|
132
|
+
output.strip.match(/^\[(.*)\]$/)[1].split(/,/)
|
133
|
+
else
|
134
|
+
[]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Forces the node to restart/reload its JavaScript VMs,
|
139
|
+
# effectively reloading any user-provided code.
|
140
|
+
def js_reload
|
141
|
+
riak_admin 'js_reload'
|
142
|
+
end
|
143
|
+
|
144
|
+
# Provides the status of members of the ring.
|
145
|
+
# @return [Hash] a collection of stats about ring members
|
146
|
+
def member_status
|
147
|
+
output = riak_admin 'member_status'
|
148
|
+
result = {}
|
149
|
+
if $?.success?
|
150
|
+
output.each_line do |line|
|
151
|
+
next if line =~ /^(?:[=-]|Status)+/ # Skip the pretty headers
|
152
|
+
if line =~ %r{^Valid:(\d+) / Leaving:(\d+) / Exiting:(\d+) / Joining:(\d+) / Down:(\d+)}
|
153
|
+
result.merge!(:valid => $1.to_i,
|
154
|
+
:leaving => $2.to_i,
|
155
|
+
:exiting => $3.to_i,
|
156
|
+
:joining => $4.to_i,
|
157
|
+
:down => $5.to_i)
|
158
|
+
else
|
159
|
+
result[:members] ||= {}
|
160
|
+
status, ring, pending, node = line.split(/\s+/)
|
161
|
+
node = $1 if node =~ /^'(.*)'$/
|
162
|
+
ring = $1.to_f if ring =~ /(\d+\.\d+)%/
|
163
|
+
result[:members][node] = {
|
164
|
+
:status => status,
|
165
|
+
:ring => ring,
|
166
|
+
:pending => (pending == '--') ? 0 : pending.to_i
|
167
|
+
}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
result
|
172
|
+
end
|
173
|
+
|
174
|
+
# @return [Array<String>] a list of node names that are also
|
175
|
+
# members of this node's cluster, and empty list if the
|
176
|
+
# {#member_status} fails or no other nodes are present
|
177
|
+
def peers
|
178
|
+
all_nodes = member_status[:members] && member_status[:members].keys.reject {|n| n == name }
|
179
|
+
all_nodes || []
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
# Runs a command using the 'riak' control script.
|
184
|
+
def riak(*commands)
|
185
|
+
`#{control_script} #{commands.join(' ')} 2>&1`
|
186
|
+
end
|
187
|
+
|
188
|
+
# Runs a command using the 'riak-admin' script.
|
189
|
+
def riak_admin(*commands)
|
190
|
+
`#{admin_script} #{commands.join(' ')} 2>&1`
|
191
|
+
end
|
192
|
+
|
193
|
+
# Waits for the HTTP port to become available, which is a better
|
194
|
+
# indication of readiness than the start script finishing.
|
195
|
+
def wait_for_startup
|
196
|
+
TCPSocket.wait_for_service_with_timeout(:host => http_ip,
|
197
|
+
:port => http_port,
|
198
|
+
:timeout => 10)
|
199
|
+
end
|
200
|
+
|
201
|
+
def wait_for_shutdown
|
202
|
+
TCPSocket.wait_for_service_termination_with_timeout(:host => http_ip,
|
203
|
+
:port => http_port,
|
204
|
+
:timeout => 10)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|