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.
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