riak-client 0.9.8 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/.gitignore +32 -0
  2. data/Gemfile +17 -11
  3. data/Guardfile +14 -0
  4. data/Rakefile +18 -44
  5. data/erl_src/riak_kv_test_backend.beam +0 -0
  6. data/erl_src/riak_kv_test_backend.erl +461 -128
  7. data/erl_src/riak_search_test_backend.beam +0 -0
  8. data/erl_src/riak_search_test_backend.erl +175 -0
  9. data/lib/active_support/cache/riak_store.rb +0 -13
  10. data/lib/riak.rb +11 -16
  11. data/lib/riak/bucket.rb +59 -41
  12. data/lib/riak/cache_store.rb +1 -14
  13. data/lib/riak/client.rb +145 -73
  14. data/lib/riak/client/beefcake/messages.rb +36 -31
  15. data/lib/riak/client/beefcake/object_methods.rb +27 -19
  16. data/lib/riak/client/beefcake_protobuffs_backend.rb +27 -33
  17. data/lib/riak/client/excon_backend.rb +0 -13
  18. data/lib/riak/client/http_backend.rb +95 -60
  19. data/lib/riak/client/http_backend/configuration.rb +144 -19
  20. data/lib/riak/client/http_backend/key_streamer.rb +1 -14
  21. data/lib/riak/client/http_backend/object_methods.rb +16 -16
  22. data/lib/riak/client/http_backend/request_headers.rb +0 -13
  23. data/lib/riak/client/http_backend/transport_methods.rb +26 -56
  24. data/lib/riak/client/net_http_backend.rb +11 -13
  25. data/lib/riak/client/protobuffs_backend.rb +21 -19
  26. data/lib/riak/client/pump.rb +1 -15
  27. data/lib/riak/client/search.rb +85 -0
  28. data/lib/riak/cluster.rb +151 -0
  29. data/lib/riak/core_ext.rb +1 -0
  30. data/lib/riak/core_ext/deep_dup.rb +13 -0
  31. data/lib/riak/core_ext/json.rb +15 -0
  32. data/lib/riak/core_ext/stringify_keys.rb +1 -1
  33. data/lib/riak/core_ext/symbolize_keys.rb +1 -1
  34. data/lib/riak/encoding.rb +6 -0
  35. data/lib/riak/failed_request.rb +2 -15
  36. data/lib/riak/i18n.rb +0 -13
  37. data/lib/riak/json.rb +19 -8
  38. data/lib/riak/link.rb +18 -20
  39. data/lib/riak/locale/en.yml +13 -16
  40. data/lib/riak/map_reduce.rb +40 -20
  41. data/lib/riak/map_reduce/filter_builder.rb +14 -18
  42. data/lib/riak/map_reduce/phase.rb +0 -13
  43. data/lib/riak/map_reduce_error.rb +0 -13
  44. data/lib/riak/node.rb +38 -0
  45. data/lib/riak/node/configuration.rb +286 -0
  46. data/lib/riak/node/console.rb +139 -0
  47. data/lib/riak/node/control.rb +207 -0
  48. data/lib/riak/node/defaults.rb +70 -0
  49. data/lib/riak/node/generation.rb +99 -0
  50. data/lib/riak/node/log.rb +34 -0
  51. data/lib/riak/node/version.rb +37 -0
  52. data/lib/riak/robject.rb +45 -41
  53. data/lib/riak/search.rb +2 -161
  54. data/lib/riak/serializers.rb +74 -0
  55. data/lib/riak/stamp.rb +77 -0
  56. data/lib/riak/test_server.rb +56 -220
  57. data/lib/riak/util/escape.rb +58 -17
  58. data/lib/riak/util/headers.rb +2 -15
  59. data/lib/riak/util/multipart.rb +0 -13
  60. data/lib/riak/util/multipart/stream_parser.rb +0 -13
  61. data/lib/riak/util/tcp_socket_extensions.rb +1 -14
  62. data/lib/riak/util/translation.rb +0 -13
  63. data/lib/riak/version.rb +3 -0
  64. data/lib/riak/walk_spec.rb +0 -13
  65. data/riak-client.gemspec +27 -47
  66. data/spec/fixtures/multipart-with-marked-tombstones.txt +17 -0
  67. data/spec/fixtures/multipart-with-unmarked-tombstone.txt +16 -0
  68. data/spec/integration/riak/cache_store_spec.rb +2 -40
  69. data/spec/integration/riak/cluster_spec.rb +88 -0
  70. data/spec/integration/riak/http_backends_spec.rb +6 -30
  71. data/spec/integration/riak/node_spec.rb +184 -0
  72. data/spec/integration/riak/protobuffs_backends_spec.rb +2 -26
  73. data/spec/integration/riak/test_server_spec.rb +31 -167
  74. data/spec/riak/beefcake_protobuffs_backend_spec.rb +5 -4
  75. data/spec/riak/bucket_spec.rb +26 -36
  76. data/spec/riak/client_spec.rb +44 -38
  77. data/spec/riak/escape_spec.rb +56 -30
  78. data/spec/riak/excon_backend_spec.rb +4 -17
  79. data/spec/riak/headers_spec.rb +1 -14
  80. data/spec/riak/http_backend/configuration_spec.rb +211 -34
  81. data/spec/riak/http_backend/object_methods_spec.rb +52 -18
  82. data/spec/riak/http_backend/transport_methods_spec.rb +5 -38
  83. data/spec/riak/http_backend_spec.rb +84 -78
  84. data/spec/riak/link_spec.rb +19 -18
  85. data/spec/riak/map_reduce/filter_builder_spec.rb +1 -14
  86. data/spec/riak/map_reduce/phase_spec.rb +1 -14
  87. data/spec/riak/map_reduce_spec.rb +141 -43
  88. data/spec/riak/multipart_spec.rb +1 -14
  89. data/spec/riak/net_http_backend_spec.rb +2 -15
  90. data/spec/riak/robject_spec.rb +129 -97
  91. data/spec/riak/search_spec.rb +45 -62
  92. data/spec/riak/serializers_spec.rb +93 -0
  93. data/spec/riak/stamp_spec.rb +54 -0
  94. data/spec/riak/stream_parser_spec.rb +3 -16
  95. data/spec/riak/walk_spec_spec.rb +1 -14
  96. data/spec/spec_helper.rb +22 -27
  97. data/spec/support/http_backend_implementation_examples.rb +49 -79
  98. data/spec/support/integration_setup.rb +10 -0
  99. data/spec/support/mock_server.rb +0 -14
  100. data/spec/support/mocks.rb +0 -13
  101. data/spec/support/test_server.rb +30 -0
  102. data/spec/support/test_server.yml.example +14 -2
  103. data/spec/support/unified_backend_examples.rb +36 -27
  104. metadata +100 -31
  105. data/lib/riak/client/curb_backend.rb +0 -89
  106. data/spec/riak/curb_backend_spec.rb +0 -76
@@ -0,0 +1,139 @@
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
+ @winch = Signal.trap("WINCH", &method(:handle_winch))
34
+ @prompt = /\(#{Regexp.escape(nodename)}\)\d+>\s*/
35
+ pipedir = Pathname(pipedir)
36
+ pipedir.children.each do |path|
37
+ if path.pipe?
38
+ if path.fnmatch("*.r") # Read pipe
39
+ # debug "Found read pipe: #{path}"
40
+ @rfile ||= path
41
+ elsif path.fnmatch("*.w") # Write pipe
42
+ # debug "Found write pipe: #{path}"
43
+ @wfile ||= path
44
+ end
45
+ else
46
+ debug "Non-pipe found! #{path}"
47
+ end
48
+ end
49
+ raise ArgumentError, t('no_pipes', :path => pipedir.to_s) if [@rfile, @wfile].any? {|p| p.nil? }
50
+ # We have to open the read pipe AFTER we have sent some data
51
+ # to the write pipe, otherwise JRuby hangs.
52
+ begin
53
+ debug "Opening write pipe."
54
+ @w = open_write_pipe
55
+ @w.sync = true
56
+ debug "Opening read pipe."
57
+ @r = open_read_pipe
58
+ command 'ok.'
59
+ debug "Initialized console: #{@r.inspect} #{@w.inspect}"
60
+ rescue => e
61
+ debug e.message
62
+ close
63
+ raise t('no_pipes', :path => pipedir.to_s) + "[ #{e.message} ]"
64
+ end
65
+ end
66
+
67
+ # Sends an Erlang command to the console
68
+ # @param [String] cmd an Erlang expression to send to the node
69
+ def command(cmd)
70
+ @mutex.synchronize do
71
+ begin
72
+ debug "Sending command #{cmd.inspect}"
73
+ @w.print "#{cmd}\n"
74
+ wait_for_erlang_prompt
75
+ rescue SystemCallError
76
+ close
77
+ end
78
+ end
79
+ end
80
+
81
+ # Detects whether the console connection is still open, that is,
82
+ # if the node hasn't disconnected from the other side of the
83
+ # pipe.
84
+ def open?
85
+ (@r && !@r.closed?) && (@w && !@w.closed?)
86
+ end
87
+
88
+ # Scans the output of the console until an Erlang shell prompt
89
+ # is found. Called by {#command} to ensure that the submitted
90
+ # command succeeds.
91
+ def wait_for_erlang_prompt
92
+ wait_for @prompt
93
+ end
94
+
95
+ # Scans the output of the console for the given pattern.
96
+ # @param [String, Regexp] pattern the pattern to scan for
97
+ def wait_for(pattern)
98
+ debug "Scanning for #{pattern.inspect}"
99
+ @r.expect(pattern)
100
+ end
101
+
102
+ # Closes the console by detaching from the pipes.
103
+ def close
104
+ @r.close if @r && !@r.closed?
105
+ @w.close if @w && !@w.closed?
106
+ Signal.trap("WINCH", @winch)
107
+ freeze
108
+ end
109
+
110
+ protected
111
+ # Handles the "window change" signal by faking it.
112
+ def handle_winch
113
+ debug "WINCHED!"
114
+ @w.print "\033_winsize=80,26\033\\"
115
+ Signal.trap("WINCH", &method(:handle_winch))
116
+ end
117
+
118
+ def debug(msg)
119
+ $stderr.puts msg if ENV["DEBUG_RIAK_CONSOLE"]
120
+ end
121
+
122
+ def open_write_pipe
123
+ if defined?(::Java)
124
+ java.io.FileOutputStream.new(@wfile.to_s).to_io
125
+ else
126
+ @wfile.open(File::WRONLY|File::NONBLOCK)
127
+ end
128
+ end
129
+
130
+ def open_read_pipe
131
+ if defined?(::Java)
132
+ IO.popen("cat #{@rfile}", "rb")
133
+ else
134
+ @rfile.open(File::RDONLY|File::NONBLOCK)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ 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
@@ -0,0 +1,70 @@
1
+ require 'riak/core_ext/deep_dup'
2
+
3
+ module Riak
4
+ class Node
5
+ # Settings based on Riak master/1.0.
6
+ ENV_DEFAULTS = {
7
+ :riak_core => {
8
+ :ring_creation_size => 64
9
+ },
10
+ :riak_kv => {
11
+ :storage_backend => :riak_kv_bitcask_backend,
12
+ :map_js_vm_count => 8,
13
+ :reduce_js_vm_count => 6,
14
+ :hook_js_vm_count => 2,
15
+ :mapper_batch_size => 5,
16
+ :js_max_vm_mem => 8,
17
+ :js_thread_stack => 16,
18
+ :riak_kv_stat => true,
19
+ :legacy_stats => false,
20
+ :vnode_vclocks => true,
21
+ :http_url_encoding => :on,
22
+ :legacy_keylisting => false,
23
+ :mapred_system => :pipe,
24
+ :add_paths => []
25
+ },
26
+ :riak_search => {
27
+ :enabled => true
28
+ },
29
+ :luwak => {
30
+ :enabled => true
31
+ },
32
+ :merge_index => {
33
+ :buffer_rollover_size => 1048576,
34
+ :max_compact_segments => 20
35
+ },
36
+ :eleveldb => {},
37
+ :bitcask => {},
38
+ :lager => {
39
+ :crash_log_size => 65536,
40
+ :error_logger_redirect => true
41
+ },
42
+ :riak_sysmon => {
43
+ :process_limit => 30,
44
+ :port_limit => 30,
45
+ :gc_ms_limit => 50,
46
+ :heap_word_limit => 10485760
47
+ },
48
+ :sasl => {
49
+ :sasl_error_logger => false
50
+ }
51
+ }.freeze
52
+
53
+ # Based on Riak master/1.0.
54
+ VM_DEFAULTS = {
55
+ "+K" => true,
56
+ "+A" => 64,
57
+ "-smp" => "enable",
58
+ "+W" => "w",
59
+ "-env ERL_MAX_PORTS" => 4096,
60
+ "-env ERL_FULLSWEEP_AFTER" => 0
61
+ }.freeze
62
+
63
+ protected
64
+ # Populates the defaults
65
+ def set_defaults
66
+ @env = ENV_DEFAULTS.deep_dup
67
+ @vm = VM_DEFAULTS.deep_dup
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,99 @@
1
+ require 'yaml'
2
+
3
+ module Riak
4
+ class Node
5
+ # Does the node exist on disk?
6
+ def exist?
7
+ manifest.exist?
8
+ end
9
+
10
+ # Deletes the node and regenerates it.
11
+ def recreate
12
+ delete
13
+ create
14
+ end
15
+
16
+ # Generates the node.
17
+ def create
18
+ unless exist?
19
+ create_directories
20
+ write_scripts
21
+ write_vm_args
22
+ write_app_config
23
+ write_manifest
24
+ end
25
+ end
26
+
27
+ # Clears data from known data directories. Stops the node if it is
28
+ # running.
29
+ def drop
30
+ was_started = started?
31
+ stop if was_started
32
+ data.children.each {|dir| dir.children.each {|c| c.rmtree } }
33
+ start if was_started
34
+ end
35
+
36
+ # Removes the node from disk and freezes the object.
37
+ def destroy
38
+ delete
39
+ freeze
40
+ end
41
+
42
+ protected
43
+ def delete
44
+ stop unless stopped?
45
+ root.rmtree if root.exist?
46
+ end
47
+
48
+ def create_directories
49
+ root.mkpath
50
+ NODE_DIRECTORIES.each {|d| send(d).mkpath }
51
+ end
52
+
53
+ def write_vm_args
54
+ (etc + 'vm.args').open('w') do |f|
55
+ vm.each do |k,v|
56
+ f.puts "#{k} #{v}"
57
+ end
58
+ end
59
+ end
60
+
61
+ def write_app_config
62
+ (etc + 'app.config').open('w') do |f|
63
+ f.write to_erlang_config(env) + '.'
64
+ end
65
+ end
66
+
67
+ def write_scripts
68
+ [control_script, admin_script].each {|s| write_script(s.basename, s) }
69
+ end
70
+
71
+ def write_script(name, target)
72
+ source_script = source + name
73
+ target.open('wb') do |f|
74
+ source_script.readlines.each do |line|
75
+ line.sub!(/(RUNNER_SCRIPT_DIR=)(.*)/, '\1' + bin.to_s)
76
+ line.sub!(/(RUNNER_ETC_DIR=)(.*)/, '\1' + etc.to_s)
77
+ line.sub!(/(RUNNER_USER=)(.*)/, '\1')
78
+ line.sub!(/(RUNNER_LOG_DIR=)(.*)/, '\1' + log.to_s)
79
+ line.sub!(/(PIPE_DIR=)(.*)/, '\1' + pipe.to_s)
80
+ line.sub!('grep "$RUNNER_BASE_DIR/.*/[b]eam"', 'grep "$RUNNER_ETC_DIR/app.config"')
81
+ if line.strip == "RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*}"
82
+ line = "RUNNER_BASE_DIR=#{source.parent.to_s}\n"
83
+ end
84
+ f.write line
85
+ end
86
+ end
87
+ target.chmod 0755
88
+ end
89
+
90
+ def write_manifest
91
+ # TODO: For now this only saves the information that was used when
92
+ # configuring the node. Later we'll verify/warn if the settings
93
+ # used differ on subsequent generations.
94
+ @configuration[:env] = @env
95
+ @configuration[:vm] = @vm
96
+ manifest.open('w') {|f| YAML.dump(@configuration, f) }
97
+ end
98
+ end
99
+ end