riak-client 1.0.0.beta → 1.0.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 (69) hide show
  1. data/.gitignore +7 -4
  2. data/Gemfile +12 -17
  3. data/Guardfile +1 -1
  4. data/LICENSE +16 -0
  5. data/README.markdown +178 -0
  6. data/RELEASE_NOTES.md +99 -0
  7. data/Rakefile +25 -1
  8. data/erl_src/riak_kv_test014_backend.beam +0 -0
  9. data/erl_src/riak_kv_test014_backend.erl +189 -0
  10. data/erl_src/riak_kv_test_backend.beam +0 -0
  11. data/erl_src/riak_kv_test_backend.erl +37 -19
  12. data/lib/riak/client.rb +322 -272
  13. data/lib/riak/client/beefcake_protobuffs_backend.rb +6 -10
  14. data/lib/riak/client/decaying.rb +28 -0
  15. data/lib/riak/client/excon_backend.rb +27 -11
  16. data/lib/riak/client/http_backend.rb +71 -2
  17. data/lib/riak/client/http_backend/configuration.rb +17 -3
  18. data/lib/riak/client/http_backend/transport_methods.rb +3 -3
  19. data/lib/riak/client/net_http_backend.rb +18 -14
  20. data/lib/riak/client/node.rb +111 -0
  21. data/lib/riak/client/pool.rb +180 -0
  22. data/lib/riak/client/protobuffs_backend.rb +15 -5
  23. data/lib/riak/client/search.rb +9 -3
  24. data/lib/riak/link.rb +5 -7
  25. data/lib/riak/locale/en.yml +1 -0
  26. data/lib/riak/node/configuration.rb +1 -0
  27. data/lib/riak/node/defaults.rb +19 -6
  28. data/lib/riak/node/generation.rb +9 -2
  29. data/lib/riak/node/log.rb +2 -2
  30. data/lib/riak/node/version.rb +22 -16
  31. data/lib/riak/robject.rb +19 -3
  32. data/lib/riak/serializers.rb +1 -1
  33. data/lib/riak/test_server.rb +10 -2
  34. data/lib/riak/version.rb +1 -1
  35. data/riak-client.gemspec +3 -3
  36. data/spec/failover/failover.rb +59 -0
  37. data/spec/integration/riak/http_backends_spec.rb +2 -2
  38. data/spec/integration/riak/node_spec.rb +16 -24
  39. data/spec/integration/riak/protobuffs_backends_spec.rb +1 -1
  40. data/spec/integration/riak/test_server_spec.rb +4 -3
  41. data/spec/integration/riak/threading_spec.rb +193 -0
  42. data/spec/riak/beefcake_protobuffs_backend/object_methods_spec.rb +23 -0
  43. data/spec/riak/beefcake_protobuffs_backend_spec.rb +4 -2
  44. data/spec/riak/bucket_spec.rb +2 -1
  45. data/spec/riak/client_spec.rb +80 -181
  46. data/spec/riak/excon_backend_spec.rb +3 -2
  47. data/spec/riak/http_backend/configuration_spec.rb +37 -5
  48. data/spec/riak/http_backend/object_methods_spec.rb +1 -1
  49. data/spec/riak/http_backend/transport_methods_spec.rb +2 -2
  50. data/spec/riak/http_backend_spec.rb +53 -3
  51. data/spec/riak/map_reduce_spec.rb +1 -1
  52. data/spec/riak/net_http_backend_spec.rb +1 -2
  53. data/spec/riak/node_spec.rb +173 -0
  54. data/spec/riak/pool_spec.rb +306 -0
  55. data/spec/riak/robject_spec.rb +8 -4
  56. data/spec/riak/search_spec.rb +66 -15
  57. data/spec/riak/serializers_spec.rb +12 -1
  58. data/spec/spec_helper.rb +9 -1
  59. data/spec/support/http_backend_implementation_examples.rb +6 -2
  60. data/spec/support/sometimes.rb +46 -0
  61. data/spec/support/test_server.rb +50 -19
  62. data/spec/support/unified_backend_examples.rb +11 -10
  63. data/spec/support/version_filter.rb +14 -0
  64. metadata +40 -29
  65. data/lib/active_support/cache/riak_store.rb +0 -2
  66. data/lib/riak/cache_store.rb +0 -84
  67. data/lib/riak/client/pump.rb +0 -30
  68. data/lib/riak/util/fiber1.8.rb +0 -48
  69. data/spec/integration/riak/cache_store_spec.rb +0 -129
@@ -3,7 +3,6 @@ require 'riak/json'
3
3
  require 'riak/client'
4
4
  require 'riak/failed_request'
5
5
  require 'riak/client/protobuffs_backend'
6
- require 'riak/client/pump'
7
6
 
8
7
  module Riak
9
8
  class Client
@@ -91,11 +90,10 @@ module Riak
91
90
  req = RpbListKeysReq.new(:bucket => maybe_encode(bucket))
92
91
  write_protobuff(:ListKeysReq, req)
93
92
  keys = []
94
- pump = Pump.new(block) if block_given?
95
93
  while msg = decode_response
96
94
  break if msg.done
97
- if pump
98
- pump.pump msg.keys
95
+ if block_given?
96
+ yield msg.keys
99
97
  else
100
98
  keys += msg.keys
101
99
  end
@@ -107,13 +105,10 @@ module Riak
107
105
  req = RpbMapRedReq.new(:request => mr.to_json, :content_type => "application/json")
108
106
  write_protobuff(:MapRedReq, req)
109
107
  results = []
110
- pump = Pump.new(lambda do |msg|
111
- block.call msg.phase, JSON.parse(msg.response)
112
- end) if block_given?
113
108
  while msg = decode_response
114
109
  break if msg.done
115
- if pump
116
- pump.pump msg
110
+ if block_given?
111
+ yield msg.phase, JSON.parse(msg.response)
117
112
  else
118
113
  results[msg.phase] ||= []
119
114
  results[msg.phase] += JSON.parse(msg.response)
@@ -176,7 +171,8 @@ module Riak
176
171
  end
177
172
  rescue SystemCallError, SocketError => e
178
173
  reset_socket
179
- raise Riak::ProtobuffsFailedRequest.new(:server_error, e.message)
174
+ raise
175
+ #raise Riak::ProtobuffsFailedRequest.new(:server_error, e.message)
180
176
  end
181
177
  end
182
178
  end
@@ -0,0 +1,28 @@
1
+ class Riak::Client::Decaying
2
+ # A float which decays exponentially with time.
3
+
4
+ attr_accessor :e
5
+ attr_accessor :p
6
+ # @param[:p] The initial value
7
+ # @param[:e] Exponent base
8
+ # @param[:r] Timescale
9
+ def initialize(opts = {})
10
+ @p = opts[:p] || 0
11
+ @e = opts[:e] || Math::E
12
+ @r = opts[:r] || Math.log(0.5) / 10
13
+ @t0 = Time.now
14
+ end
15
+
16
+ # Add d to current value.
17
+ def <<(d)
18
+ @p = value + d
19
+ end
20
+
21
+ # Return current value
22
+ def value
23
+ now = Time.now
24
+ dt = now - @t0
25
+ @t0 = now
26
+ @p = @p * (@e ** (@r * dt))
27
+ end
28
+ end
@@ -1,8 +1,6 @@
1
-
2
1
  require 'riak/failed_request'
3
2
  require 'riak/client/http_backend'
4
3
  require 'riak/client/http_backend/request_headers'
5
- require 'riak/client/pump'
6
4
 
7
5
  module Riak
8
6
  class Client
@@ -13,15 +11,36 @@ module Riak
13
11
  def self.configured?
14
12
  begin
15
13
  require 'excon'
16
- Excon::VERSION >= "0.5.7"
14
+ Client::NETWORK_ERRORS << Excon::Errors::SocketError
15
+ Client::NETWORK_ERRORS.uniq!
16
+ Excon::VERSION >= "0.5.7" && patch_excon
17
17
  rescue LoadError
18
18
  false
19
19
  end
20
20
  end
21
21
 
22
+ # Adjusts Excon's connection collection to allow multiple
23
+ # connections to the same host from the same Thread. Instead we
24
+ # use the Riak::Client::Pool to segregate connections.
25
+ # @note This can be changed when Excon has a proper pool of its own.
26
+ def self.patch_excon
27
+ unless defined? @@patched
28
+ ::Excon::Connection.class_eval do
29
+ def sockets
30
+ @sockets ||= {}
31
+ end
32
+ end
33
+ end
34
+ @@patched = true
35
+ end
36
+
37
+ def teardown
38
+ connection.reset
39
+ end
40
+
22
41
  private
23
42
  def perform(method, uri, headers, expect, data=nil, &block)
24
- configure_ssl if @client.ssl_enabled?
43
+ configure_ssl if @node.ssl_enabled?
25
44
 
26
45
  params = {
27
46
  :method => method.to_s.upcase,
@@ -32,11 +51,8 @@ module Riak
32
51
  params[:body] = data if [:put,:post].include?(method)
33
52
  params[:idempotent] = (method != :post)
34
53
 
35
- if block_given?
36
- pump = Pump.new(block)
37
- # Later versions of Excon pass multiple arguments to the block
38
- block = lambda {|*args| pump.pump(args.first) }
39
- end
54
+ # Later versions of Excon pass multiple arguments to the block
55
+ block = lambda {|*args| yield args.first } if block_given?
40
56
 
41
57
  response = connection.request(params, &block)
42
58
  response_headers.initialize_http_header(response.headers)
@@ -57,8 +73,8 @@ module Riak
57
73
  end
58
74
 
59
75
  def configure_ssl
60
- Excon.ssl_verify_peer = @client.ssl_options[:verify_mode].to_s === "peer"
61
- Excon.ssl_ca_path = @client.ssl_options[:ca_path] if @client.ssl_options[:ca_path]
76
+ Excon.ssl_verify_peer = @node.ssl_options[:verify_mode].to_s === "peer"
77
+ Excon.ssl_ca_path = @node.ssl_options[:ca_path] if @node.ssl_options[:ca_path]
62
78
  end
63
79
  end
64
80
  end
@@ -29,11 +29,17 @@ module Riak
29
29
  # The Riak::Client that uses this backend
30
30
  attr_reader :client
31
31
 
32
+ # The Riak::Client::Node that uses this backend
33
+ attr_reader :node
34
+
32
35
  # Create an HTTPBackend for the Riak::Client.
33
- # @param [Client] client the client
34
- def initialize(client)
36
+ # @param [Client] The client
37
+ # @param [Node] The node we're connecting to.
38
+ def initialize(client, node)
35
39
  raise ArgumentError, t("client_type", :client => client) unless Client === client
40
+ raise ArgumentError, t("node_type", :node => node) unless Node === node
36
41
  @client = client
42
+ @node = node
37
43
  end
38
44
 
39
45
  # Pings the server
@@ -246,6 +252,69 @@ module Riak
246
252
  def update_search_index(index, updates)
247
253
  post(200, solr_update_path(index), updates, {'Content-Type' => 'text/xml'})
248
254
  end
255
+
256
+ # (Luwak) Fetches a file from the Luwak large-file interface.
257
+ # @param [String] filename the name of the file
258
+ # @yield [chunk] A block which will receive individual chunks of
259
+ # the file as they are streamed
260
+ # @yieldparam [String] chunk a block of the file
261
+ # @return [IO, nil] the file (also having content_type and
262
+ # original_filename accessors). The file will need to be
263
+ # reopened to be read
264
+ def get_file(filename, &block)
265
+ if block_given?
266
+ get(200, luwak_path(filename), &block)
267
+ nil
268
+ else
269
+ tmpfile = LuwakFile.new(escape(filename))
270
+ begin
271
+ response = get(200, luwak_path(filename)) do |chunk|
272
+ tmpfile.write chunk
273
+ end
274
+ tmpfile.content_type = response[:headers]['content-type'].first
275
+ tmpfile
276
+ ensure
277
+ tmpfile.close
278
+ end
279
+ end
280
+ end
281
+
282
+ # (Luwak) Detects whether a file exists in the Luwak large-file
283
+ # interface.
284
+ # @param [String] filename the name of the file
285
+ # @return [true,false] whether the file exists
286
+ def file_exists?(filename)
287
+ result = head([200,404], luwak_path(filename))
288
+ result[:code] == 200
289
+ end
290
+
291
+ # (Luwak) Deletes a file from the Luwak large-file interface.
292
+ # @param [String] filename the name of the file
293
+ def delete_file(filename)
294
+ delete([204,404], luwak_path(filename))
295
+ end
296
+
297
+ # (Luwak) Uploads a file to the Luwak large-file interface.
298
+ # @overload store_file(filename, content_type, data)
299
+ # Stores the file at the given key/filename
300
+ # @param [String] filename the key/filename for the object
301
+ # @param [String] content_type the MIME Content-Type for the data
302
+ # @param [IO, String] data the contents of the file
303
+ # @overload store_file(content_type, data)
304
+ # Stores the file with a server-determined key/filename
305
+ # @param [String] content_type the MIME Content-Type for the data
306
+ # @param [String, #read] data the contents of the file
307
+ # @return [String] the key/filename where the object was stored
308
+ def store_file(*args)
309
+ data, content_type, filename = args.reverse
310
+ if filename
311
+ put(204, luwak_path(filename), data, {"Content-Type" => content_type})
312
+ filename
313
+ else
314
+ response = post(201, luwak_path(nil), data, {"Content-Type" => content_type})
315
+ response[:headers]["location"].first.split("/").last
316
+ end
317
+ end
249
318
  end
250
319
  end
251
320
  end
@@ -132,6 +132,16 @@ module Riak
132
132
  end
133
133
  end
134
134
 
135
+ # @return [URI] a URL path for the Luwak interface
136
+ def luwak_path(key)
137
+ raise t('luwak_unsupported') unless luwak_wm_file
138
+ if key
139
+ path(luwak_wm_file, escape(key))
140
+ else
141
+ path(luwak_wm_file)
142
+ end
143
+ end
144
+
135
145
  private
136
146
  def server_config
137
147
  @server_config ||= {}.tap do |hash|
@@ -152,15 +162,15 @@ module Riak
152
162
  end
153
163
 
154
164
  def riak_kv_wm_raw
155
- server_config[:riak_kv_wm_raw] || client.http_paths[:prefix]
165
+ server_config[:riak_kv_wm_raw] || node.http_paths[:prefix]
156
166
  end
157
167
 
158
168
  def riak_kv_wm_link_walker
159
- server_config[:riak_kv_wm_link_walker] || client.http_paths[:prefix]
169
+ server_config[:riak_kv_wm_link_walker] || node.http_paths[:prefix]
160
170
  end
161
171
 
162
172
  def riak_kv_wm_mapred
163
- server_config[:riak_kv_wm_mapred] || client.http_paths[:mapred]
173
+ server_config[:riak_kv_wm_mapred] || node.http_paths[:mapred]
164
174
  end
165
175
 
166
176
  def riak_kv_wm_ping
@@ -178,6 +188,10 @@ module Riak
178
188
  def riak_solr_indexer_wm
179
189
  server_config[:riak_solr_indexer_wm]
180
190
  end
191
+
192
+ def luwak_wm_file
193
+ server_config[:luwak_wm_file]
194
+ end
181
195
  end
182
196
  end
183
197
  end
@@ -140,13 +140,13 @@ module Riak
140
140
  end
141
141
 
142
142
  def basic_auth_header
143
- @client.basic_auth ? {"Authorization" => "Basic #{Base64::encode64(@client.basic_auth)}"} : {}
143
+ @node.basic_auth ? {"Authorization" => "Basic #{Base64::encode64(@node.basic_auth)}"} : {}
144
144
  end
145
145
 
146
146
  # @return [URI] The calculated root URI for the Riak HTTP endpoint
147
147
  def root_uri
148
- protocol = client.ssl_enabled? ? "https" : "http"
149
- URI.parse("#{protocol}://#{client.host}:#{client.http_port}")
148
+ protocol = node.ssl_enabled? ? "https" : "http"
149
+ URI.parse("#{protocol}://#{node.host}:#{node.http_port}")
150
150
  end
151
151
 
152
152
  # Calculates an absolute URI from a relative path specification
@@ -27,11 +27,15 @@ module Riak
27
27
  @read_timeout ||= 4096
28
28
  end
29
29
 
30
+ # Net::HTTP doesn't use persistent connections, so there's no
31
+ # work to do here.
32
+ def teardown; end
33
+
30
34
  private
31
35
  def perform(method, uri, headers, expect, data=nil) #:nodoc:
32
36
  http = Net::HTTP.new(uri.host, uri.port)
33
37
  http.read_timeout = self.class.read_timeout
34
- configure_ssl(http) if @client.ssl_enabled?
38
+ configure_ssl(http) if @node.ssl_enabled?
35
39
 
36
40
  request = Net::HTTP.const_get(method.to_s.capitalize).new(uri.request_uri, headers)
37
41
  case data
@@ -51,28 +55,28 @@ module Riak
51
55
 
52
56
  {}.tap do |result|
53
57
  http.request(request) do |response|
54
- if valid_response?(expect, response.code)
55
- result.merge!({:headers => response.to_hash, :code => response.code.to_i})
56
- response.read_body {|chunk| yield chunk } if block_given?
57
- if return_body?(method, response.code, block_given?)
58
- result[:body] = response.body
59
- end
60
- else
58
+ unless valid_response?(expect, response.code)
61
59
  raise Riak::HTTPFailedRequest.new(method, expect, response.code.to_i, response.to_hash, response.body)
62
60
  end
61
+
62
+ result.merge!({:headers => response.to_hash, :code => response.code.to_i})
63
+ response.read_body {|chunk| yield chunk } if block_given?
64
+ if return_body?(method, response.code, block_given?)
65
+ result[:body] = response.body
66
+ end
63
67
  end
64
68
  end
65
69
  end
66
70
 
67
71
  def configure_ssl(http)
68
72
  http.use_ssl = true
69
- http.verify_mode = OpenSSL::SSL.const_get("VERIFY_#{@client.ssl_options[:verify_mode].upcase}")
70
- if @client.ssl_options[:pem]
71
- http.cert = OpenSSL::X509::Certificate.new(@client.ssl_options[:pem])
72
- http.key = OpenSSL::PKey::RSA.new(@client.ssl_options[:pem], @client.ssl_options[:pem_password])
73
+ http.verify_mode = OpenSSL::SSL.const_get("VERIFY_#{@node.ssl_options[:verify_mode].upcase}")
74
+ if @node.ssl_options[:pem]
75
+ http.cert = OpenSSL::X509::Certificate.new(@node.ssl_options[:pem])
76
+ http.key = OpenSSL::PKey::RSA.new(@node.ssl_options[:pem], @node.ssl_options[:pem_password])
73
77
  end
74
- http.ca_file = @client.ssl_options[:ca_file] if @client.ssl_options[:ca_file]
75
- http.ca_path = @client.ssl_options[:ca_path] if @client.ssl_options[:ca_path]
78
+ http.ca_file = @node.ssl_options[:ca_file] if @node.ssl_options[:ca_file]
79
+ http.ca_path = @node.ssl_options[:ca_path] if @node.ssl_options[:ca_path]
76
80
  end
77
81
  end
78
82
  end
@@ -0,0 +1,111 @@
1
+ module Riak
2
+ class Client
3
+ class Node
4
+ # Represents a single riak node in a cluster.
5
+
6
+ include Util::Translation
7
+ include Util::Escape
8
+
9
+ VALID_OPTIONS = [:host, :http_port, :pb_port, :http_paths, :prefix,
10
+ :mapred, :luwak, :solr, :port, :basic_auth, :ssl_options, :ssl]
11
+
12
+ # For a score which halves in 10 seconds, choose
13
+ # ln(1/2)/10
14
+ ERRORS_DECAY_RATE = Math.log(0.5)/10
15
+
16
+ # What IP address or hostname does this node listen on?
17
+ attr_accessor :host
18
+ # Which port does the HTTP interface listen on?
19
+ attr_accessor :http_port
20
+ # Which port does the protocol buffers interface listen on?
21
+ attr_accessor :pb_port
22
+ # A hash of HTTP paths used on this node.
23
+ attr_accessor :http_paths
24
+ # A "user:password" string.
25
+ attr_reader :basic_auth
26
+ attr_accessor :ssl_options
27
+ # A Decaying rate of errors.
28
+ attr_reader :error_rate
29
+
30
+ def initialize(client, opts = {})
31
+ @client = client
32
+ @ssl = opts[:ssl]
33
+ @ssl_options = opts[:ssl_options]
34
+ @host = opts[:host] || "127.0.0.1"
35
+ @http_port = opts[:http_port] || opts[:port] || 8098
36
+ @pb_port = opts[:pb_port] || 8087
37
+ @http_paths = {
38
+ :prefix => opts[:prefix] || "/riak/",
39
+ :mapred => opts[:mapred] || "/mapred",
40
+ :luwak => opts[:luwak] || "/luwak",
41
+ :solr => opts[:solr] || "/solr" # Unused?
42
+ }.merge(opts[:http_paths] || {})
43
+ self.basic_auth = opts[:basic_auth]
44
+
45
+ @error_rate = Decaying.new
46
+ end
47
+
48
+ def ==(o)
49
+ o.kind_of? Node and
50
+ @host == o.host and
51
+ @http_port == o.http_port and
52
+ @pb_port == o.pb_port
53
+ end
54
+
55
+ # Sets the HTTP Basic Authentication credentials.
56
+ # @param [String] value an auth string in the form "user:password"
57
+ def basic_auth=(value)
58
+ case value
59
+ when nil
60
+ @basic_auth = nil
61
+ when String
62
+ raise ArgumentError, t("invalid_basic_auth") unless value.to_s.split(':').length === 2
63
+ @basic_auth = value
64
+ end
65
+ end
66
+
67
+ # Can this node be used for HTTP requests?
68
+ def http?
69
+ # TODO: Need to sort out capabilities
70
+ true
71
+ end
72
+
73
+ # Can this node be used for protocol buffers requests?
74
+ def protobuffs?
75
+ # TODO: Need to sort out capabilities
76
+ true
77
+ end
78
+
79
+ # Enables or disables SSL on this node to be utilized by the HTTP
80
+ # Backends
81
+ def ssl=(value)
82
+ @ssl_options = Hash === value ? value : {}
83
+ value ? ssl_enable : ssl_disable
84
+ end
85
+
86
+ # Checks if SSL is enabled for HTTP
87
+ def ssl_enabled?
88
+ @client.protocol == 'https' || @ssl_options.present?
89
+ end
90
+
91
+ def ssl_enable
92
+ @client.protocol = 'https'
93
+ @ssl_options[:pem] = File.read(@ssl_options[:pem_file]) if @ssl_options[:pem_file]
94
+ @ssl_options[:verify_mode] ||= "peer" if @ssl_options.stringify_keys.any? {|k,v| %w[pem ca_file ca_path].include?(k)}
95
+ @ssl_options[:verify_mode] ||= "none"
96
+ raise ArgumentError.new(t('invalid_ssl_verify_mode', :invalid => @ssl_options[:verify_mode])) unless %w[none peer].include?(@ssl_options[:verify_mode])
97
+
98
+ @ssl_options
99
+ end
100
+
101
+ def ssl_disable
102
+ @client.protocol = 'http'
103
+ @ssl_options = nil
104
+ end
105
+
106
+ def inspect
107
+ "<#Node #{@host}:#{@http_port}:#{@pb_port}>"
108
+ end
109
+ end
110
+ end
111
+ end