riak-client 0.9.0.beta → 0.9.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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