riak-client 0.9.0.beta → 0.9.0.beta2

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 (50) hide show
  1. data/Gemfile +10 -7
  2. data/Rakefile +21 -3
  3. data/erl_src/riak_kv_test_backend.beam +0 -0
  4. data/erl_src/riak_kv_test_backend.erl +29 -13
  5. data/lib/riak/bucket.rb +1 -1
  6. data/lib/riak/cache_store.rb +1 -1
  7. data/lib/riak/client.rb +119 -8
  8. data/lib/riak/client/beefcake/messages.rb +162 -0
  9. data/lib/riak/client/beefcake/object_methods.rb +92 -0
  10. data/lib/riak/client/beefcake_protobuffs_backend.rb +186 -0
  11. data/lib/riak/client/curb_backend.rb +10 -16
  12. data/lib/riak/client/excon_backend.rb +14 -18
  13. data/lib/riak/client/http_backend.rb +13 -13
  14. data/lib/riak/client/http_backend/object_methods.rb +1 -1
  15. data/lib/riak/client/http_backend/transport_methods.rb +6 -2
  16. data/lib/riak/client/net_http_backend.rb +33 -20
  17. data/lib/riak/client/protobuffs_backend.rb +103 -0
  18. data/lib/riak/client/pump.rb +44 -0
  19. data/lib/riak/failed_request.rb +58 -3
  20. data/lib/riak/locale/en.yml +11 -3
  21. data/lib/riak/map_reduce.rb +15 -6
  22. data/lib/riak/map_reduce/filter_builder.rb +4 -4
  23. data/lib/riak/test_server.rb +5 -1
  24. data/lib/riak/util/multipart.rb +30 -16
  25. data/lib/riak/util/multipart/stream_parser.rb +74 -0
  26. data/riak-client.gemspec +14 -12
  27. data/spec/fixtures/server.cert.crt +15 -0
  28. data/spec/fixtures/server.cert.key +15 -0
  29. data/spec/fixtures/test.pem +1 -0
  30. data/spec/integration/riak/http_backends_spec.rb +45 -0
  31. data/spec/integration/riak/protobuffs_backends_spec.rb +45 -0
  32. data/spec/integration/riak/test_server_spec.rb +2 -2
  33. data/spec/riak/bucket_spec.rb +4 -4
  34. data/spec/riak/client_spec.rb +209 -3
  35. data/spec/riak/excon_backend_spec.rb +8 -7
  36. data/spec/riak/http_backend/configuration_spec.rb +64 -0
  37. data/spec/riak/http_backend/object_methods_spec.rb +1 -1
  38. data/spec/riak/http_backend/transport_methods_spec.rb +129 -0
  39. data/spec/riak/http_backend_spec.rb +13 -1
  40. data/spec/riak/map_reduce/filter_builder_spec.rb +45 -0
  41. data/spec/riak/map_reduce/phase_spec.rb +149 -0
  42. data/spec/riak/map_reduce_spec.rb +5 -5
  43. data/spec/riak/net_http_backend_spec.rb +1 -0
  44. data/spec/riak/{object_spec.rb → robject_spec.rb} +1 -1
  45. data/spec/riak/stream_parser_spec.rb +66 -0
  46. data/spec/support/drb_mock_server.rb +2 -2
  47. data/spec/support/http_backend_implementation_examples.rb +27 -0
  48. data/spec/support/mock_server.rb +22 -1
  49. data/spec/support/unified_backend_examples.rb +255 -0
  50. metadata +43 -54
@@ -54,7 +54,7 @@ module Riak
54
54
  # Load object data from an HTTP response
55
55
  # @param [Hash] response a response from {Riak::Client::HTTPBackend}
56
56
  def load_object(robject, response)
57
- extract_header(robject, response, "location", :key) {|v| URI.unescape(v.split("/").last) }
57
+ extract_header(robject, response, "location", :key) {|v| URI.unescape(v.match(%r{.*/(.*?)(\?.*)?$})[1]) }
58
58
  extract_header(robject, response, "content-type", :content_type)
59
59
  extract_header(robject, response, "x-riak-vclock", :vclock)
60
60
  extract_header(robject, response, "link", :links) {|v| Set.new(Link.parse(v)) }
@@ -138,12 +138,16 @@ module Riak
138
138
  {
139
139
  "Accept" => "multipart/mixed, application/json;q=0.7, */*;q=0.5",
140
140
  "X-Riak-ClientId" => @client.client_id
141
- }
141
+ }.merge(basic_auth_header)
142
+ end
143
+
144
+ def basic_auth_header
145
+ @client.basic_auth ? {"Authorization" => "Basic #{Base64::encode64(@client.basic_auth)}"} : {}
142
146
  end
143
147
 
144
148
  # @return [URI] The calculated root URI for the Riak HTTP endpoint
145
149
  def root_uri
146
- URI.parse("http://#{client.host}:#{client.port}")
150
+ URI.parse("#{client.protocol}://#{client.host}:#{client.port}")
147
151
  end
148
152
 
149
153
  # Calculates an absolute URI from a relative path specification
@@ -16,8 +16,6 @@ require 'riak'
16
16
  module Riak
17
17
  class Client
18
18
  # Uses the Ruby standard library Net::HTTP to connect to Riak.
19
- # We recommend using the CurbBackend, which will
20
- # be preferred when the 'curb' library is available.
21
19
  # Conforms to the Riak::Client::HTTPBackend interface.
22
20
  class NetHTTPBackend < HTTPBackend
23
21
  def self.configured?
@@ -31,30 +29,45 @@ module Riak
31
29
 
32
30
  private
33
31
  def perform(method, uri, headers, expect, data=nil) #:nodoc:
34
- Net::HTTP.start(uri.host, uri.port) do |http|
35
- request = Net::HTTP.const_get(method.to_s.capitalize).new(uri.request_uri, headers)
36
- case data
37
- when String
38
- request.body = data
39
- when IO
40
- request.body_stream = data
41
- end
32
+ http = Net::HTTP.new(uri.host, uri.port)
33
+ configure_ssl(http) if @client.ssl_enabled?
34
+
35
+ request = Net::HTTP.const_get(method.to_s.capitalize).new(uri.request_uri, headers)
36
+ case data
37
+ when String
38
+ request.body = data
39
+ when IO
40
+ request.body_stream = data
41
+ end
42
42
 
43
- {}.tap do |result|
44
- http.request(request) do |response|
45
- if valid_response?(expect, response.code)
46
- result.merge!({:headers => response.to_hash, :code => response.code.to_i})
47
- response.read_body {|chunk| yield chunk } if block_given?
48
- if return_body?(method, response.code, block_given?)
49
- result[:body] = response.body
50
- end
51
- else
52
- raise FailedRequest.new(method, expect, response.code.to_i, response.to_hash, response.body)
43
+ {}.tap do |result|
44
+ http.request(request) do |response|
45
+ if valid_response?(expect, response.code)
46
+ result.merge!({:headers => response.to_hash, :code => response.code.to_i})
47
+ response.read_body {|chunk| yield chunk } if block_given?
48
+ if return_body?(method, response.code, block_given?)
49
+ result[:body] = response.body
53
50
  end
51
+ else
52
+ raise Riak::HTTPFailedRequest.new(method, expect, response.code.to_i, response.to_hash, response.body)
54
53
  end
55
54
  end
56
55
  end
57
56
  end
57
+
58
+ def configure_ssl(http)
59
+ http.use_ssl = true
60
+ http.verify_mode = OpenSSL::SSL.const_get("VERIFY_#{@client.ssl_options[:verify_mode].upcase}")
61
+
62
+ if @client.ssl_options[:pem]
63
+ http.cert = OpenSSL::X510::Certificate.new(@client.ssl_options[:pem])
64
+ http.key = OpenSSL::PKey::RSA.new(@client.ssl_options[:pem], @client.ssl_options[:pem_password])
65
+ end
66
+
67
+ http.ca_file = @client.ssl_options[:ca_file] if @client.ssl_options[:ca_file]
68
+
69
+ http.ca_path = @client.ssl_options[:ca_path] if @client.ssl_options[:ca_path]
70
+ end
58
71
  end
59
72
  end
60
73
  end
@@ -0,0 +1,103 @@
1
+ # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'riak'
15
+ require 'socket'
16
+ require 'base64'
17
+ require 'digest/sha1'
18
+
19
+ module Riak
20
+ class Client
21
+ class ProtobuffsBackend
22
+ include Util::Translation
23
+
24
+ # Message Codes Enum
25
+ MESSAGE_CODES = %W[
26
+ ErrorResp
27
+ PingReq
28
+ PingResp
29
+ GetClientIdReq
30
+ GetClientIdResp
31
+ SetClientIdReq
32
+ SetClientIdResp
33
+ GetServerInfoReq
34
+ GetServerInfoResp
35
+ GetReq
36
+ GetResp
37
+ PutReq
38
+ PutResp
39
+ DelReq
40
+ DelResp
41
+ ListBucketsReq
42
+ ListBucketsResp
43
+ ListKeysReq
44
+ ListKeysResp
45
+ GetBucketReq
46
+ GetBucketResp
47
+ SetBucketReq
48
+ SetBucketResp
49
+ MapRedReq
50
+ MapRedResp
51
+ ].map {|s| s.intern }.freeze
52
+
53
+ def self.simple(method, code)
54
+ define_method method do
55
+ socket.write([1, MESSAGE_CODES.index(code)].pack('NC'))
56
+ decode_response
57
+ end
58
+ end
59
+
60
+ attr_accessor :client
61
+ def initialize(client)
62
+ @client = client
63
+ end
64
+
65
+ simple :ping, :PingReq
66
+ simple :get_client_id, :GetClientIdReq
67
+ simple :server_info, :GetServerInfoReq
68
+ simple :list_buckets, :ListBucketsReq
69
+
70
+ private
71
+ # Implemented by subclasses
72
+ def decode_response
73
+ raise NotImplementedError
74
+ end
75
+
76
+ def socket
77
+ Thread.current[:riakpbc_socket] ||= TCPSocket.new(@client.host, @client.port)
78
+ end
79
+
80
+ def reset_socket
81
+ socket.close
82
+ Thread.current[:riakpbc_socket] = nil
83
+ end
84
+
85
+ UINTMAX = 0xffffffff
86
+ QUORUMS = {
87
+ "one" => UINTMAX - 1,
88
+ "quorum" => UINTMAX - 2,
89
+ "all" => UINTMAX - 3,
90
+ "default" => UINTMAX - 4
91
+ }.freeze
92
+
93
+ def normalize_quorum_value(q)
94
+ QUORUMS[q.to_s] || q.to_i
95
+ end
96
+
97
+ # This doesn't give us exactly the keygen that Riak uses, but close.
98
+ def generate_key
99
+ Base64.encode64(Digest::SHA1.digest(Socket.gethostname + Time.now.iso8601(3))).tr("+/","-_").sub(/=+\n$/,'')
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'riak'
15
+ begin
16
+ require 'fiber'
17
+ rescue LoadError
18
+ require 'riak/util/fiber1.8'
19
+ end
20
+
21
+ module Riak
22
+ class Client
23
+ # @private
24
+ class Pump
25
+ def initialize(block)
26
+ @fiber = Fiber.new do
27
+ loop do
28
+ block.call Fiber.yield
29
+ end
30
+ end
31
+ @fiber.resume
32
+ end
33
+
34
+ def pump(input)
35
+ @fiber.resume input
36
+ input.size if input.respond_to?(:size) # for curb
37
+ end
38
+
39
+ def to_proc
40
+ method(:pump).to_proc
41
+ end
42
+ end
43
+ end
44
+ end
@@ -14,10 +14,19 @@
14
14
  require 'riak'
15
15
 
16
16
  module Riak
17
- # Exception raised when the expected response code from Riak
18
- # fails to match the actual response code.
17
+ # Exception raised when receiving an unexpected client response from
18
+ # Riak.
19
19
  class FailedRequest < StandardError
20
20
  include Util::Translation
21
+
22
+ def initialize(message)
23
+ super(message || t('failed_request'))
24
+ end
25
+ end
26
+
27
+ # Exception raised when the expected HTTP response code from Riak
28
+ # fails to match the actual response code.
29
+ class HTTPFailedRequest < FailedRequest
21
30
  # @return [Symbol] the HTTP method, one of :head, :get, :post, :put, :delete
22
31
  attr_reader :method
23
32
  # @return [Fixnum] the expected response code
@@ -31,7 +40,53 @@ module Riak
31
40
 
32
41
  def initialize(method, expected_code, received_code, headers, body)
33
42
  @method, @expected, @code, @headers, @body = method, expected_code, received_code, headers, body
34
- super t("failed_request", :expected => @expected.inspect, :code => @code, :body => @body)
43
+ super t("http_failed_request", :expected => @expected.inspect, :code => @code, :body => @body)
44
+ end
45
+
46
+ def is_json?
47
+ headers['content-type'].include?('application/json')
48
+ end
49
+
50
+ # @return [true,false] whether the error represents a "not found" response
51
+ def not_found?
52
+ @code.to_i == 404
53
+ end
54
+
55
+ # @return [true,false] whether the error represents an internal
56
+ # server error
57
+ def server_error?
58
+ @code.to_i == 500
59
+ end
60
+ end
61
+
62
+ # Exception raised when receiving an unexpected Protocol Buffers response from Riak
63
+ class ProtobuffsFailedRequest < FailedRequest
64
+ def initialize(code, message)
65
+ super t('protobuffs_failed_request', :code => code, :body => message)
66
+ @original_message = message
67
+ @not_found = code == :not_found
68
+ @server_error = code == :server_error
69
+ end
70
+
71
+ # @return [true, false] whether the error response is in JSON
72
+ def is_json?
73
+ begin
74
+ JSON.parse(original_message)
75
+ true
76
+ rescue
77
+ false
78
+ end
79
+ end
80
+
81
+ # @return [true,false] whether the error represents a "not found" response
82
+ def not_found?
83
+ @not_found
84
+ end
85
+
86
+ # @return [true,false] whether the error represents an internal
87
+ # server error
88
+ def server_error?
89
+ @server_error
35
90
  end
36
91
  end
37
92
  end
@@ -17,24 +17,32 @@ en:
17
17
  client_type: "invalid argument %{client} is not a Riak::Client"
18
18
  content_type_undefined: "content_type is not defined!"
19
19
  empty_map_reduce_query: "Specify one or more query phases to your MapReduce."
20
- failed_request: "Expected %{expected} from Riak but received %{code}. %{body}"
20
+ failed_request: "Client request failed."
21
21
  filter_needs_block: "Filter %{filter} expects a block."
22
22
  filter_arity_mismatch: "Filter %{filter} expects %{expected} arguments but %{received} were given."
23
23
  hash_type: "invalid argument %{hash} is not a Hash"
24
24
  http_configuration: "The %{backend} HTTP backend cannot be used. Please check its requirements."
25
+ http_failed_request: "Expected %{expected} from Riak but received %{code}. %{body}"
25
26
  hostname_invalid: "host must be a valid hostname"
27
+ protocol_invalid: "'%{invalid}' is not a valid protocol, valid values are %{valid}"
28
+ invalid_basic_auth: "basic auth must be set using 'user:pass'"
26
29
  invalid_client_id: "Invalid client ID, must be a string or between 0 and %{max_id}"
27
30
  invalid_function_value: "invalid value for function: %{value}"
28
31
  invalid_phase_type: "type must be :map, :reduce, or :link"
29
- loading_bucket: "while loading bucket '%{name}'"
32
+ invalid_options: "Invalid configuration options given."
33
+ loading_bucket: "while loading bucket '%{name}'"
34
+ missing_block: "A block must be given."
30
35
  missing_host_and_port: "You must specify a host and port, or use the defaults of 127.0.0.1:8098"
31
36
  module_function_pair_required: "function must have two elements when an array"
37
+ not_found: "The requested object was not found."
32
38
  path_and_body_required: "You must supply both a resource path and a body."
33
39
  port_invalid: "port must be an integer between 0 and 65535"
34
- request_body_type: "Request body must be a string or IO."
40
+ protobuffs_failed_request: "Expected success from Riak but received %{code}. %{body}"
41
+ request_body_type: "Request body must be a String or IO."
35
42
  resource_path_short: "Resource path too short"
36
43
  search_docs_require_id: "Search index documents must include the 'id' field."
37
44
  search_remove_requires_id_or_query: "Search index documents to be removed must have 'id' or 'query' keys."
45
+ stale_write_prevented: "Stale write prevented by client."
38
46
  stored_function_invalid: "function must have :bucket and :key when a hash"
39
47
  string_type: "invalid_argument %{string} is not a String"
40
48
  too_few_arguments: "too few arguments: %{params}"
@@ -83,7 +83,7 @@ module Riak
83
83
  bucket = params.shift
84
84
  bucket = bucket.name if Bucket === bucket
85
85
  if Array === params.first
86
- @inputs = {:bucket => escape(bucket), :filters => params.first }
86
+ @inputs = {:bucket => escape(bucket), :key_filters => params.first }
87
87
  else
88
88
  key = params.shift
89
89
  @inputs << params.unshift(escape(key)).unshift(escape(bucket))
@@ -171,15 +171,24 @@ module Riak
171
171
  end
172
172
 
173
173
  # Executes this map-reduce job.
174
- # @return [Array<Array>] similar to link-walking, each element is
175
- # an array of results from a phase where "keep" is true. If there
176
- # is only one "keep" phase, only the results from that phase will
177
- # be returned.
174
+ # @overload run
175
+ # Return the entire collection of results.
176
+ # @return [Array<Array>] similar to link-walking, each element is
177
+ # an array of results from a phase where "keep" is true. If there
178
+ # is only one "keep" phase, only the results from that phase will
179
+ # be returned.
180
+ # @overload run
181
+ # Stream the results through the given block without accumulating.
182
+ # @yield [phase, data] A block to stream results through
183
+ # @yieldparam [Fixnum] phase the phase from which the results were
184
+ # generated
185
+ # @yieldparam [Array] data a list of results from the phase
186
+ # @return [nil] nothing
178
187
  def run(&block)
179
188
  raise MapReduceError.new(t("empty_map_reduce_query")) if @query.empty?
180
189
  @client.backend.mapred(self, &block)
181
190
  rescue FailedRequest => fr
182
- if fr.code == 500 && fr.headers['content-type'].include?("application/json")
191
+ if fr.server_error? && fr.is_json?
183
192
  raise MapReduceError.new(fr.body)
184
193
  else
185
194
  raise fr
@@ -44,7 +44,7 @@ module Riak
44
44
  :matches => 1,
45
45
  :neq => 1,
46
46
  :eq => 1,
47
- :set_member => 1,
47
+ :set_member => -1,
48
48
  :similar_to => 2,
49
49
  :starts_with => 1,
50
50
  :ends_with => 1
@@ -58,8 +58,8 @@ module Riak
58
58
  # FilterBuilder.new do
59
59
  # string_to_int
60
60
  # AND do
61
- # greater_than_eq 50
62
- # neq 100
61
+ # seq { greater_than_eq 50 }
62
+ # seq { neq 100 }
63
63
  # end
64
64
  # end
65
65
  LOGICAL_OPERATIONS = %w{and or not}
@@ -67,7 +67,7 @@ module Riak
67
67
  FILTERS.each do |f,arity|
68
68
  class_eval <<-CODE
69
69
  def #{f}(*args)
70
- raise ArgumentError.new(t("filter_arity_mismatch", :filter => :#{f}, :expected => #{arity.inspect}, :received => args.size)) unless Array(#{arity.inspect}).include?(args.size)
70
+ raise ArgumentError.new(t("filter_arity_mismatch", :filter => :#{f}, :expected => #{arity.inspect}, :received => args.size)) unless #{arity.inspect} == -1 || Array(#{arity.inspect}).include?(args.size)
71
71
  @filters << ([:#{f}] + args)
72
72
  end
73
73
  CODE
@@ -33,7 +33,10 @@ module Riak
33
33
  :js_vm_count => 8,
34
34
  :js_max_vm_mem => 8,
35
35
  :js_thread_stack => 16,
36
- :riak_kv_stat => true
36
+ :riak_kv_stat => true,
37
+ # Turn off map caching
38
+ :map_cache_size => 0, # 0.14
39
+ :vnode_cache_entries => 0 # 0.13
37
40
  },
38
41
  :luwak => {
39
42
  :enabled => false
@@ -65,6 +68,7 @@ module Riak
65
68
  @vm_args = options[:vm_args]
66
69
  # For synchronizing start/stop/recycle
67
70
  @mutex = Mutex.new
71
+ cleanup # Should prevent some errors related to unclean startup
68
72
  end
69
73
 
70
74
  # Sets up the proper scripts, configuration and directories for