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
Binary file
@@ -52,7 +52,9 @@
52
52
  is_empty/1,
53
53
  status/1,
54
54
  callback/3,
55
- reset/0]).
55
+ reset/0,
56
+ capabilities/1,
57
+ capabilities/2]).
56
58
 
57
59
  -ifdef(TEST).
58
60
  -include_lib("eunit/include/eunit.hrl").
@@ -79,7 +81,7 @@
79
81
  -spec reset() -> ok | {error, timeout}.
80
82
  reset() ->
81
83
  {ok, Ring} = riak_core_ring_manager:get_my_ring(),
82
- [ ets:delete_all_objects(list_to_atom("kv" ++ integer_to_list(P))) ||
84
+ [ catch ets:delete_all_objects(list_to_atom("kv" ++ integer_to_list(P))) ||
83
85
  P <- riak_core_ring:my_indices(Ring) ],
84
86
  ok.
85
87
 
@@ -87,28 +89,44 @@ reset() ->
87
89
 
88
90
  %% @doc Return the major version of the
89
91
  %% current API and a capabilities list.
90
- -spec api_version() -> {integer(), [atom()]}.
92
+ -spec api_version() -> {ok, integer()} | {integer(), [atom()]}.
91
93
  api_version() ->
92
- {?API_VERSION, ?CAPABILITIES}.
94
+ case lists:member({capabilities, 1}, riak_kv_backend:behaviour_info(callbacks)) of
95
+ true -> % Using 1.1 API or later
96
+ {ok, ?API_VERSION};
97
+ _ -> % Using 1.0 API
98
+ {?API_VERSION, ?CAPABILITIES}
99
+ end.
100
+
101
+ %% @doc Return the capabilities of the backend.
102
+ -spec capabilities(state()) -> {ok, [atom()]}.
103
+ capabilities(_) ->
104
+ {ok, ?CAPABILITIES}.
105
+
106
+ %% @doc Return the capabilities of the backend.
107
+ -spec capabilities(riak_object:bucket(), state()) -> {ok, [atom()]}.
108
+ capabilities(_, _) ->
109
+ {ok, ?CAPABILITIES}.
93
110
 
94
111
  %% @doc Start the memory backend
95
112
  -spec start(integer(), config()) -> {ok, state()}.
96
- start(Partition, Config) ->
97
- TTL = config_value(ttl, Config),
98
- MemoryMB = config_value(max_memory, Config),
99
- case MemoryMB of
100
- undefined ->
101
- MaxMemory = undefined,
102
- TimeRef = undefined;
103
- _ ->
104
- MaxMemory = MemoryMB * 1024 * 1024,
105
- TimeRef = ets:new(list_to_atom(integer_to_list(Partition)), [ordered_set])
106
- end,
113
+ start(Partition, _Config) ->
114
+ %% TTL = config_value(ttl, Config),
115
+ %% MemoryMB = config_value(max_memory, Config),
116
+ %% case MemoryMB of
117
+ %% undefined ->
118
+ %% MaxMemory = undefined,
119
+ %% TimeRef = undefined;
120
+ %% _ ->
121
+ %% MaxMemory = MemoryMB * 1024 * 1024,
122
+ %% TimeRef = ets:new(list_to_atom(integer_to_list(Partition)), [ordered_set])
123
+ %% end,
107
124
  DataRef = ets:new(list_to_atom("kv" ++ integer_to_list(Partition)), [named_table, public]),
108
- {ok, #state{data_ref=DataRef,
109
- max_memory=MaxMemory,
110
- time_ref=TimeRef,
111
- ttl=TTL}}.
125
+ {ok, #state{data_ref=DataRef
126
+ %% max_memory=MaxMemory,
127
+ %% time_ref=TimeRef,
128
+ %% ttl=TTL
129
+ }}.
112
130
 
113
131
  %% @doc Stop the memory backend
114
132
  -spec stop(state()) -> ok.
data/lib/riak/client.rb CHANGED
@@ -4,6 +4,9 @@ require 'riak'
4
4
  require 'riak/util/translation'
5
5
  require 'riak/util/escape'
6
6
  require 'riak/failed_request'
7
+ require 'riak/client/pool'
8
+ require 'riak/client/decaying'
9
+ require 'riak/client/node'
7
10
  require 'riak/client/search'
8
11
  require 'riak/client/http_backend'
9
12
  require 'riak/client/net_http_backend'
@@ -28,44 +31,50 @@ module Riak
28
31
  # Regexp for validating hostnames, lifted from uri.rb in Ruby 1.8.6
29
32
  HOST_REGEX = /^(?:(?:(?:[a-zA-Z\d](?:[-a-zA-Z\d]*[a-zA-Z\d])?)\.)*(?:[a-zA-Z](?:[-a-zA-Z\d]*[a-zA-Z\d])?)\.?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-fA-F\d]{1,4}:)*[a-fA-F\d]{1,4})?::(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)\])$/n
30
33
 
31
- VALID_OPTIONS = [:protocol, :host, :port, :http_port, :pb_port, :prefix, :client_id, :mapred, :luwak, :solr, :http_paths, :http_backend, :protobuffs_backend, :ssl, :basic_auth]
34
+ # Valid constructor options.
35
+ VALID_OPTIONS = [:protocol, :nodes, :client_id, :http_backend, :protobuffs_backend] | Node::VALID_OPTIONS
36
+
37
+ # Network errors.
38
+ NETWORK_ERRORS = [
39
+ EOFError,
40
+ Errno::ECONNABORTED,
41
+ Errno::ECONNREFUSED,
42
+ Errno::ECONNRESET,
43
+ Errno::ENETDOWN,
44
+ Errno::ENETRESET,
45
+ Errno::ENETUNREACH,
46
+ SocketError,
47
+ SystemCallError,
48
+ ]
32
49
 
33
50
  # @return [String] The protocol to use for the Riak endpoint
34
51
  attr_reader :protocol
35
52
 
36
- # @return [String] The host or IP address for the Riak endpoint
37
- attr_reader :host
38
-
39
- # @return [Fixnum] The HTTP(S) port for the Riak endpoint
40
- attr_reader :http_port
41
-
42
- # @return [Fixnum] The Protocol Buffers port for the Riak endpoint
43
- attr_reader :pb_port
44
-
45
- # @return [String] The user:pass for http basic authentication
46
- attr_reader :basic_auth
47
-
48
- # @return [Hash|nil] The SSL options that get built when using SSL
49
- attr_reader :ssl_options
53
+ # @return [Array] The set of Nodes this client can communicate with.
54
+ attr_accessor :nodes
50
55
 
51
56
  # @return [String] The internal client ID used by Riak to route responses
52
57
  attr_reader :client_id
53
58
 
54
- # @attr_writer [Hash|nil] The writer that will build valid SSL options
55
- # from the provided config
56
- attr_writer :ssl
57
-
58
- # @return [Hash] HTTP path configuration.
59
- attr_accessor :http_paths
60
-
61
59
  # @return [Symbol] The HTTP backend/client to use
62
60
  attr_accessor :http_backend
63
61
 
62
+ # @return [Client::Pool] A pool of HTTP connections
63
+ attr_reader :http_pool
64
+
64
65
  # @return [Symbol] The Protocol Buffers backend/client to use
65
66
  attr_accessor :protobuffs_backend
66
67
 
68
+ # @return [Client::Pool] A pool of protobuffs connections
69
+ attr_reader :protobuffs_pool
70
+
67
71
  # Creates a client connection to Riak
68
72
  # @param [Hash] options configuration options for the client
73
+ # @option options [Array] :nodes A list of nodes this client connects to.
74
+ # Each element of the list is a hash which is passed to Node.new, e.g.
75
+ # {host: '127.0.0.1', pb_port: 1234, ...}.
76
+ # If no nodes are given, a single node is constructed from the remaining
77
+ # options given to Client.new.
69
78
  # @option options [String] :host ('127.0.0.1') The host or IP address for the Riak endpoint
70
79
  # @option options [Fixnum] :http_port (8098) The port of the Riak HTTP endpoint
71
80
  # @option options [Fixnum] :pb_port (8087) The port of the Riak Protocol Buffers endpoint
@@ -76,192 +85,56 @@ module Riak
76
85
  # @option options [String, Symbol] :protobuffs_backend (:Beefcake) which Protocol Buffers backend to use
77
86
  # @raise [ArgumentError] raised if any invalid options are given
78
87
  def initialize(options={})
79
- unless (options.keys - VALID_OPTIONS).empty?
80
- raise ArgumentError, t("invalid_options")
88
+ if options.include? :port
89
+ warn(t('deprecated.port', :backtrace => caller[0..2].join("\n ")))
81
90
  end
82
- self.protocol = options[:protocol] || "http"
83
- self.ssl = options[:ssl] if options[:ssl]
84
- self.host = options[:host] || "127.0.0.1"
85
- self.http_port = options[:http_port] || 8098
86
- self.pb_port = options[:pb_port] || 8087
87
- self.port = options[:port] if options[:port]
88
- self.http_paths = {
89
- prefix: options[:prefix] || "/riak/",
90
- mapred: options[:mapred] || "/mapred",
91
- luwak: options[:luwak] || "/luwak",
92
- solr: options[:solr] || "/solr" # Unused?
93
- }.merge(options[:http_paths] || {})
94
- self.http_backend = options[:http_backend] || :NetHTTP
95
- self.protobuffs_backend = options[:protobuffs_backend] || :Beefcake
96
- self.basic_auth = options[:basic_auth] if options[:basic_auth]
97
- self.client_id = options[:client_id] if options[:client_id]
98
- end
99
91
 
100
- # Set the client ID for this client. Must be a string or Fixnum value 0 =< value < MAX_CLIENT_ID.
101
- # @param [String, Fixnum] value The internal client ID used by Riak to route responses
102
- # @raise [ArgumentError] when an invalid client ID is given
103
- # @return [String] the assigned client ID
104
- def client_id=(value)
105
- value = case value
106
- when 0...MAX_CLIENT_ID, String
107
- value
108
- else
109
- raise ArgumentError, t("invalid_client_id", :max_id => MAX_CLIENT_ID)
110
- end
111
- backend.set_client_id value if backend.respond_to?(:set_client_id)
112
- @client_id = value
113
- end
114
-
115
- def client_id
116
- @client_id ||= backend.respond_to?(:get_client_id) ? backend.get_client_id : make_client_id
117
- end
118
-
119
- # Set the protocol of the Riak endpoint. Value must be in the
120
- # Riak::Client::PROTOCOLS array.
121
- # @raise [ArgumentError] if the protocol is not in PROTOCOLS
122
- # @return [String] the protocol being assigned
123
- def protocol=(value)
124
- unless PROTOCOLS.include?(value.to_s)
125
- raise ArgumentError, t("protocol_invalid", :invalid => value, :valid => PROTOCOLS.join(', '))
92
+ unless (evil = options.keys - VALID_OPTIONS).empty?
93
+ raise ArgumentError, "#{evil.inspect} are not valid options for Client.new"
126
94
  end
127
- @ssl_options ||= {} if value == 'https'
128
- @ssl_options = nil if value == 'http'
129
- @backend = nil
130
- @protocol = value
131
- end
132
-
133
- # Set the hostname of the Riak endpoint. Must be an IPv4, IPv6, or valid hostname
134
- # @param [String] value The host or IP address for the Riak endpoint
135
- # @raise [ArgumentError] if an invalid hostname is given
136
- # @return [String] the assigned hostname
137
- def host=(value)
138
- raise ArgumentError, t("hostname_invalid") unless String === value && value.present? && value =~ HOST_REGEX
139
- @host = value
140
- end
141
95
 
142
- # @return [Fixnum] The port of the Riak endpoint
143
- # @deprecated Ports for HTTP(S) and Protocol Buffers are
144
- # segregated. Use {#http_port} or {#pb_port}.
145
- def port
146
- warn(t('deprecated.port', :backtrace => caller.join("\n")))
147
- case protocol
148
- when /http/i
149
- http_port
150
- when /pbc/i
151
- pb_port
152
- end
153
- end
154
-
155
- # Set the port number of the Riak endpoint. This must be an
156
- # integer between 0 and 65535.
157
- # @deprecated Ports for HTTP(S) and Protocol Buffers are
158
- # segregated. Use {#http_port=} or {#pb_port=}.
159
- # @param [Fixnum] value The port number of the Riak endpoint
160
- # @raise [ArgumentError] if an invalid port number is given
161
- # @return [Fixnum] the assigned port number
162
- def port=(value)
163
- warn(t('deprecated.port', :backtrace => caller[0..2].join("\n ")))
164
- raise ArgumentError, t("port_invalid") unless (0..65535).include?(value)
165
- case protocol
166
- when /http/i
167
- self.http_port = value
168
- when /pbc/i
169
- self.pb_port = value
96
+ @nodes = (options[:nodes] || []).map do |n|
97
+ Client::Node.new self, n
98
+ end
99
+ if @nodes.empty? or options[:host] or options[:http_port] or options[:pb_port]
100
+ @nodes |= [Client::Node.new(self, options)]
170
101
  end
171
- end
172
-
173
- # Set the HTTP(S) port for the Riak endpoint
174
- # @param [Fixnum] value The HTTP port number of the Riak endpoint
175
- # @raise [ArgumentError] if an invalid port number is given
176
- # @return [Fixnum] the assigned port number
177
- def http_port=(value)
178
- raise ArgumentError, t("port_invalid") unless (0..65535).include?(value)
179
- @http_port = value
180
- end
181
-
182
- # Set the Protocol Buffers port for the Riak endpoint
183
- # @param [Fixnum] value The Protocol Buffers port number of the Riak endpoint
184
- # @raise [ArgumentError] if an invalid port number is given
185
- # @return [Fixnum] the assigned port number
186
- def pb_port=(value)
187
- raise ArgumentError, t("port_invalid") unless (0..65535).include?(value)
188
- @pb_port = value
189
- end
190
-
191
102
 
192
- # Sets the HTTP Basic Authentication credentials.
193
- # @param [String] value an auth string in the form "user:password"
194
- def basic_auth=(value)
195
- raise ArgumentError, t("invalid_basic_auth") unless value.to_s.split(':').length === 2
196
- @basic_auth = value
197
- end
103
+ @protobuffs_pool = Pool.new(
104
+ method(:new_protobuffs_backend),
105
+ lambda { |b| b.teardown }
106
+ )
198
107
 
199
- # Sets the desired HTTP backend
200
- def http_backend=(value)
201
- @http, @backend = nil, nil
202
- @http_backend = value
203
- end
108
+ @http_pool = Pool.new(
109
+ method(:new_http_backend),
110
+ lambda { |b| b.teardown }
111
+ )
204
112
 
205
- # Sets the desired Protocol Buffers backend
206
- def protobuffs_backend=(value)
207
- @protobuffs, @backend = nil, nil
208
- @protobuffs_backend = value
113
+ self.protocol = options[:protocol] || "http"
114
+ self.http_backend = options[:http_backend] || :NetHTTP
115
+ self.protobuffs_backend = options[:protobuffs_backend] || :Beefcake
116
+ self.client_id = options[:client_id] if options[:client_id]
209
117
  end
210
118
 
211
- # Enables or disables SSL on the client to be utilized by the HTTP Backends
212
- def ssl=(value)
213
- @ssl_options = Hash === value ? value : {}
214
- value ? ssl_enable : ssl_disable
215
- end
216
-
217
- # Checks if SSL is enabled for HTTP
218
- def ssl_enabled?
219
- protocol == 'https' || @ssl_options.present?
220
- end
221
-
222
- # Automatically detects and returns an appropriate HTTP backend.
223
- # The HTTP backend is used internally by the Riak client, but can also
224
- # be used to access the server directly.
225
- # @return [HTTPBackend] the HTTP backend for this client
226
- def http
227
- @http ||= begin
228
- klass = self.class.const_get("#{@http_backend}Backend")
229
- if klass.configured?
230
- klass.new(self)
231
- else
232
- raise t('http_configuration', :backend => @http_backend)
233
- end
234
- end
235
- end
236
-
237
- # Automatically detects and returns an appropriate Protocol
238
- # Buffers backend. The Protocol Buffers backend is used
239
- # internally by the Riak client but can also be used to access the
240
- # server directly.
241
- # @return [ProtobuffsBackend] the Protocol Buffers backend for
242
- # this client
243
- def protobuffs
244
- @protobuffs ||= begin
245
- klass = self.class.const_get("#{@protobuffs_backend}ProtobuffsBackend")
246
- if klass.configured?
247
- klass.new(self)
248
- else
249
- raise t('protobuffs_configuration', :backend => @protobuffs_backend)
250
- end
251
- end
252
- end
253
-
254
- # Returns a backend for operations that are protocol-independent.
119
+ # Yields a backend for operations that are protocol-independent.
255
120
  # You can change which type of backend is used by setting the
256
121
  # {#protocol}.
257
- # @return [HTTPBackend,ProtobuffsBackend] an appropriate client backend
258
- def backend
259
- @backend ||= case @protocol.to_s
260
- when /https?/i
261
- http
262
- when /pbc/i
263
- protobuffs
264
- end
122
+ # @yield [HTTPBackend,ProtobuffsBackend] an appropriate client backend
123
+ def backend(&block)
124
+ case @protocol.to_s
125
+ when /https?/i
126
+ http &block
127
+ when /pbc/i
128
+ protobuffs &block
129
+ end
130
+ end
131
+
132
+ # Sets basic HTTP auth on all nodes.
133
+ def basic_auth=(auth)
134
+ @nodes.each do |node|
135
+ node.basic_auth = auth
136
+ end
137
+ auth
265
138
  end
266
139
 
267
140
  # Retrieves a bucket from Riak.
@@ -286,34 +159,90 @@ module Riak
286
159
  # @return [Array<Bucket>] a list of buckets
287
160
  def buckets
288
161
  warn(t('list_buckets', :backtrace => caller.join("\n "))) unless Riak.disable_list_keys_warnings
289
- backend.list_buckets.map {|name| Bucket.new(self, name) }
162
+ backend do |b|
163
+ b.list_buckets.map {|name| Bucket.new(self, name) }
164
+ end
290
165
  end
291
166
  alias :list_buckets :buckets
292
167
 
168
+ # Choose a node from a set.
169
+ def choose_node(nodes = self.nodes)
170
+ # Prefer nodes which have gone a reasonable time without errors.
171
+ s = nodes.select do |node|
172
+ node.error_rate.value < 0.1
173
+ end
174
+
175
+ if s.empty?
176
+ # Fall back to minimally broken node.
177
+ nodes.min_by do |node|
178
+ node.error_rate.value
179
+ end
180
+ else
181
+ s[rand(s.size)]
182
+ end
183
+ end
184
+
185
+ # Set the client ID for this client. Must be a string or Fixnum value 0 =<
186
+ # value < MAX_CLIENT_ID.
187
+ # @param [String, Fixnum] value The internal client ID used by Riak to route responses
188
+ # @raise [ArgumentError] when an invalid client ID is given
189
+ # @return [String] the assigned client ID
190
+ def client_id=(value)
191
+ value = case value
192
+ when 0...MAX_CLIENT_ID, String
193
+ value
194
+ else
195
+ raise ArgumentError, t("invalid_client_id", :max_id => MAX_CLIENT_ID)
196
+ end
197
+
198
+ # Change all existing backend client IDs.
199
+ @protobuffs_pool.each do |pb|
200
+ pb.set_client_id value if pb.respond_to?(:set_client_id)
201
+ end
202
+ @client_id = value
203
+ end
204
+
205
+ def client_id
206
+ @client_id ||= backend do |b|
207
+ if b.respond_to?(:get_client_id)
208
+ b.get_client_id
209
+ else
210
+ make_client_id
211
+ end
212
+ end
213
+ end
214
+
293
215
  # Deletes a file stored via the "Luwak" interface
294
216
  # @param [String] filename the key/filename to delete
295
217
  def delete_file(filename)
296
- http.delete([204,404], http_paths[:luwak], escape(filename))
218
+ http do |h|
219
+ h.delete_file(filename)
220
+ end
297
221
  true
298
222
  end
299
-
223
+
300
224
  # Delete an object. See Bucket#delete
301
225
  def delete_object(bucket, key, options = {})
302
- backend.delete_object(bucket, key, options)
226
+ backend do |b|
227
+ b.delete_object(bucket, key, options)
228
+ end
303
229
  end
304
230
 
305
231
  # Checks whether a file exists in "Luwak".
306
232
  # @param [String] key the key to check
307
233
  # @return [true, false] whether the key exists in "Luwak"
308
234
  def file_exists?(key)
309
- result = http.head([200,404], http_paths[:luwak], escape(key))
310
- result[:code] == 200
235
+ http do |h|
236
+ h.file_exists?(key)
237
+ end
311
238
  end
312
239
  alias :file_exist? :file_exists?
313
240
 
314
241
  # Bucket properties. See Bucket#props
315
242
  def get_bucket_props(bucket)
316
- backend.get_bucket_props bucket
243
+ backend do |b|
244
+ b.get_bucket_props bucket
245
+ end
317
246
  end
318
247
 
319
248
  # Retrieves a large file/IO object from Riak via the "Luwak"
@@ -328,98 +257,225 @@ module Riak
328
257
  # from the method.
329
258
  # @yieldparam [String] chunk a single chunk of the object's data
330
259
  def get_file(filename, &block)
331
- if block_given?
332
- http.get(200, http_paths[:luwak], escape(filename), &block)
333
- nil
334
- else
335
- tmpfile = LuwakFile.new(escape(filename))
336
- begin
337
- response = http.get(200, http_paths[:luwak], escape(filename)) do |chunk|
338
- tmpfile.write chunk
339
- end
340
- tmpfile.content_type = response[:headers]['content-type'].first
341
- tmpfile
342
- ensure
343
- tmpfile.close
344
- end
260
+ http do |h|
261
+ h.get_file(filename, &block)
345
262
  end
346
263
  end
347
264
 
348
265
  # Queries a secondary index on a bucket. See Bucket#get_index
349
266
  def get_index(bucket, index, query)
350
- backend.get_index bucket, index, query
267
+ backend do |b|
268
+ b.get_index bucket, index, query
269
+ end
351
270
  end
352
-
271
+
353
272
  # Get an object. See Bucket#get
354
273
  def get_object(bucket, key, options = {})
355
- backend.fetch_object(bucket, key, options)
274
+ backend do |b|
275
+ b.fetch_object(bucket, key, options)
276
+ end
277
+ end
278
+
279
+ # Yields an HTTPBackend.
280
+ def http(&block)
281
+ recover_from @http_pool, &block
282
+ end
283
+
284
+ # Sets the desired HTTP backend
285
+ def http_backend=(value)
286
+ @http_backend = value
287
+ # Shut down existing connections using the old backend
288
+ @http_pool.clear
289
+ @http_backend
356
290
  end
357
291
 
358
292
  # @return [String] A representation suitable for IRB and debugging output.
359
293
  def inspect
360
- "#<Riak::Client #{protocol}://#{host}:#{protocol == 'pbc' ? pb_port : http_port}>"
294
+ "#<Riak::Client #{nodes.inspect}>"
295
+ end
296
+
297
+ # Link-walk.
298
+ def link_walk(object, specs)
299
+ http do |h|
300
+ h.link_walk object, specs
301
+ end
361
302
  end
362
303
 
363
304
  # Retrieves a list of keys in the given bucket. See Bucket#keys
364
305
  def list_keys(bucket, &block)
365
306
  if block_given?
366
- backend.list_keys bucket, &block
307
+ backend do |b|
308
+ b.list_keys bucket, &block
309
+ end
367
310
  else
368
- backend.list_keys bucket
311
+ backend do |b|
312
+ b.list_keys bucket
313
+ end
369
314
  end
370
315
  end
371
-
372
- # Deprecated accessor for http_paths[:luwak]
373
- def luwak
374
- http_paths[:luwak]
316
+
317
+ # Executes a mapreduce request. See MapReduce#run
318
+ def mapred(mr, &block)
319
+ backend do |b|
320
+ b.mapred(mr, &block)
321
+ end
375
322
  end
376
323
 
377
- # Deprecated accessor for http_paths[:luwak]
378
- def luwak=(luwak)
379
- http_paths[:luwak] = luwak
324
+ # Creates a new HTTP backend.
325
+ # @return [HTTPBackend] An HTTP backend for a given node.
326
+ def new_http_backend
327
+ klass = self.class.const_get("#{@http_backend}Backend")
328
+ if klass.configured?
329
+ node = choose_node(
330
+ @nodes.select do |n|
331
+ n.http?
332
+ end
333
+ )
334
+
335
+ klass.new(self, node)
336
+ else
337
+ raise t('http_configuration', :backend => @http_backend)
338
+ end
380
339
  end
381
340
 
382
- # Executes a mapreduce request. See MapReduce#run
383
- def mapred(mr, &block)
384
- backend.mapred(mr, &block)
341
+ # Creates a new protocol buffers backend.
342
+ # @return [ProtobuffsBackend] the Protocol Buffers backend for
343
+ # a given node.
344
+ def new_protobuffs_backend
345
+ klass = self.class.const_get("#{@protobuffs_backend}ProtobuffsBackend")
346
+ if klass.configured?
347
+ node = choose_node(
348
+ @nodes.select do |n|
349
+ n.protobuffs?
350
+ end
351
+ )
352
+
353
+ klass.new(self, node)
354
+ else
355
+ raise t('protobuffs_configuration', :backend => @protobuffs_backend)
356
+ end
385
357
  end
386
358
 
387
- # Pings the Riak server to check for liveness.
388
- # @return [true,false] whether the Riak server is alive and reachable
359
+ # @return [Node] An arbitrary Node.
360
+ def node
361
+ nodes[rand nodes.size]
362
+ end
363
+
364
+ # Pings the Riak cluster to check for liveness.
365
+ # @return [true,false] whether the Riak cluster is alive and reachable
389
366
  def ping
390
- backend.ping
367
+ backend do |b|
368
+ b.ping
369
+ end
391
370
  end
392
-
393
- # Deprecated accessor for http_paths[:prefix]
394
- def prefix
395
- http_paths[:prefix]
371
+
372
+ # Yields a protocol buffers backend.
373
+ def protobuffs(&block)
374
+ recover_from @protobuffs_pool, &block
396
375
  end
397
376
 
398
- # Deprecated accessor http_paths[:prefix]
399
- def prefix=(prefix)
400
- http_paths[:prefix] = prefix
377
+ # Sets the desired Protocol Buffers backend
378
+ def protobuffs_backend=(value)
379
+ # Shutdown any connections using the old backend
380
+ @protobuffs_backend = value
381
+ @protobuffs_pool.clear
382
+ @protobuffs_backend
401
383
  end
402
384
 
403
- # Reloads the object from Riak.
404
- def reload_object(object, options = {})
405
- backend.reload_object(object, options)
385
+ # Set the protocol of the Riak endpoint. Value must be in the
386
+ # Riak::Client::PROTOCOLS array.
387
+ # @raise [ArgumentError] if the protocol is not in PROTOCOLS
388
+ # @return [String] the protocol being assigned
389
+ def protocol=(value)
390
+ unless PROTOCOLS.include?(value.to_s)
391
+ raise ArgumentError, t("protocol_invalid", :invalid => value, :valid => PROTOCOLS.join(', '))
392
+ end
393
+
394
+ case value
395
+ when 'https'
396
+ nodes.each do |node|
397
+ node.ssl_options ||= {}
398
+ end
399
+ when 'http'
400
+ nodes.each do |node|
401
+ node.ssl_options = nil
402
+ end
403
+ end
404
+
405
+ #TODO
406
+ @backend = nil
407
+ @protocol = value
406
408
  end
407
-
408
- # Deprecated accessor for http_paths[:solr]
409
- def solr
410
- http_paths[:solr]
409
+
410
+ # Takes a pool. Acquires a backend from the pool and yields it with
411
+ # node-specific error recovery.
412
+ def recover_from(pool)
413
+ skip_nodes = []
414
+ take_opts = {}
415
+ tries = 3
416
+
417
+ begin
418
+ # Only select nodes which we haven't used before.
419
+ unless skip_nodes.empty?
420
+ take_opts[:filter] = lambda do |backend|
421
+ not skip_nodes.include? backend.node
422
+ end
423
+ end
424
+
425
+ # Acquire a backend
426
+ pool.take(take_opts) do |backend|
427
+ begin
428
+ yield backend
429
+ rescue *NETWORK_ERRORS => e
430
+ # Network error.
431
+ tries -= 1
432
+
433
+ # Notify the node that a request against it failed.
434
+ backend.node.error_rate << 1
435
+
436
+ # Skip this node next time.
437
+ skip_nodes << backend.node
438
+
439
+ # And delete this connection.
440
+ raise Pool::BadResource, e
441
+ end
442
+ end
443
+ rescue Pool::BadResource => e
444
+ retry if tries > 0
445
+ raise e.message
446
+ end
411
447
  end
412
448
 
413
- # Deprecated accessor for http_paths[:solr]
414
- def solr=(solr)
415
- http_paths[:solr] = solr
449
+ # Reloads the object from Riak.
450
+ def reload_object(object, options = {})
451
+ backend do |b|
452
+ b.reload_object(object, options)
453
+ end
416
454
  end
417
455
 
418
456
  # Sets the properties on a bucket. See Bucket#props=
419
457
  def set_bucket_props(bucket, properties)
420
- backend.set_bucket_props(bucket, properties)
458
+ # A bug in Beefcake is still giving us trouble with default booleans.
459
+ # Until it is resolved, we'll use the HTTP backend.
460
+ http do |b|
461
+ b.set_bucket_props(bucket, properties)
462
+ end
421
463
  end
422
-
464
+
465
+ # Enables or disables SSL on all nodes, for HTTP backends.
466
+ def ssl=(value)
467
+ @nodes.each do |node|
468
+ node.ssl = value
469
+ end
470
+
471
+ if value
472
+ @protocol = 'https'
473
+ else
474
+ @protocol = 'http'
475
+ end
476
+ value
477
+ end
478
+
423
479
  # Exposes a {Stamp} object for use in generating unique
424
480
  # identifiers.
425
481
  # @return [Stamp] an ID generator
@@ -440,20 +496,17 @@ module Riak
440
496
  # @param [String, #read] data the contents of the file
441
497
  # @return [String] the key/filename where the object was stored
442
498
  def store_file(*args)
443
- data, content_type, filename = args.reverse
444
- if filename
445
- http.put(204, http_paths[:luwak], escape(filename), data, {"Content-Type" => content_type})
446
- filename
447
- else
448
- response = http.post(201, http_paths[:luwak], data, {"Content-Type" => content_type})
449
- response[:headers]["location"].first.split("/").last
499
+ http do |h|
500
+ h.store_file(*args)
450
501
  end
451
502
  end
452
503
 
453
504
  # Stores an object in Riak.
454
505
  def store_object(object, options = {})
455
506
  params = {:returnbody => true}.merge(options)
456
- backend.store_object(object, params)
507
+ backend do |b|
508
+ b.store_object(object, params)
509
+ end
457
510
  end
458
511
 
459
512
  private
@@ -462,18 +515,15 @@ module Riak
462
515
  end
463
516
 
464
517
  def ssl_enable
465
- self.protocol = 'https'
466
- @ssl_options[:pem] = File.read(@ssl_options[:pem_file]) if @ssl_options[:pem_file]
467
- @ssl_options[:verify_mode] ||= "peer" if @ssl_options.stringify_keys.any? {|k,v| %w[pem ca_file ca_path].include?(k)}
468
- @ssl_options[:verify_mode] ||= "none"
469
- raise ArgumentError.new(t('invalid_ssl_verify_mode', :invalid => @ssl_options[:verify_mode])) unless %w[none peer].include?(@ssl_options[:verify_mode])
470
-
471
- @ssl_options
518
+ @nodes.each do |n|
519
+ n.ssl_enable
520
+ end
472
521
  end
473
522
 
474
523
  def ssl_disable
475
- self.protocol = 'http'
476
- @ssl_options = nil
524
+ @nodes.each do |n|
525
+ n.ssl_disable
526
+ end
477
527
  end
478
528
 
479
529
  # @private