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
@@ -0,0 +1,92 @@
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
+
16
+ module Riak
17
+ class Client
18
+ class BeefcakeProtobuffsBackend
19
+ module ObjectMethods
20
+ # Returns RpbPutReq
21
+ def dump_object(robject)
22
+ pbuf = RpbPutReq.new(:bucket => robject.bucket.name)
23
+ pbuf.key = robject.key || generate_key
24
+ pbuf.vclock = Base64.decode64(robject.vclock) if robject.vclock
25
+ pbuf.content = RpbContent.new(:value => robject.raw_data,
26
+ :content_type => robject.content_type,
27
+ :links => robject.links.map {|l| encode_link(l) }.compact)
28
+
29
+ pbuf.content.usermeta = robject.meta.map {|k,v| encode_meta(k,v)} if robject.meta.any?
30
+ pbuf.content.vtag = robject.etag if robject.etag.present?
31
+ if robject.raw_data.respond_to?(:encoding) # 1.9 support
32
+ pbuf.content.charset = robject.raw_data.encoding.name
33
+ end
34
+ pbuf
35
+ end
36
+
37
+ # Returns RObject
38
+ def load_object(pbuf, robject)
39
+ robject.vclock = Base64.encode64(pbuf.vclock).chomp if pbuf.vclock
40
+ if pbuf.content.size > 1
41
+ robject.conflict = true
42
+ robject.siblings = pbuf.content.map do |c|
43
+ sibling = RObject.new(robject.bucket, robject.key)
44
+ sibling.vclock = robject.vclock
45
+ load_content(c, sibling)
46
+ end
47
+ else
48
+ load_content(pbuf.content.first, robject)
49
+ end
50
+ robject
51
+ end
52
+
53
+ private
54
+ def load_content(pbuf, robject)
55
+ if pbuf.value.respond_to?(:force_encoding) && pbuf.charset.present?
56
+ pbuf.value.force_encoding(pbuf.charset) if Encoding.find(pbuf.charset)
57
+ end
58
+ robject.raw_data = pbuf.value
59
+ robject.etag = pbuf.vtag if pbuf.vtag.present?
60
+ robject.content_type = pbuf.content_type if pbuf.content_type.present?
61
+ robject.links = pbuf.links.map(&method(:decode_link)) if pbuf.links.present?
62
+ pbuf.usermeta.each {|pair| decode_meta(pair, robject.meta) } if pbuf.usermeta.present?
63
+ if pbuf.last_mod.present?
64
+ robject.last_modified = Time.at(pbuf.last_mod)
65
+ robject.last_modified += pbuf.last_mod_usecs / 1000000 if pbuf.last_mod_usecs.present?
66
+ end
67
+ robject
68
+ end
69
+
70
+ def decode_link(pbuf)
71
+ Riak::Link.new(pbuf.bucket, pbuf.key, pbuf.tag)
72
+ end
73
+
74
+ def encode_link(link)
75
+ return nil unless link.key.present?
76
+ RpbLink.new(:bucket => link.bucket.to_s, :key => link.key.to_s, :tag => link.tag.to_s)
77
+ end
78
+
79
+ def decode_meta(pbuf, hash)
80
+ hash[pbuf.key] = pbuf.value
81
+ end
82
+
83
+ def encode_meta(key,value)
84
+ return nil unless value.present?
85
+ RpbPair.new(:key.to_s, :value => value.to_s)
86
+ end
87
+ end
88
+
89
+ include ObjectMethods
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,186 @@
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 'riak/client/protobuffs_backend'
16
+ require 'riak/client/pump'
17
+
18
+ module Riak
19
+ class Client
20
+ class BeefcakeProtobuffsBackend < ProtobuffsBackend
21
+ def self.configured?
22
+ begin
23
+ require 'beefcake'
24
+ require 'riak/client/beefcake/messages'
25
+ require "riak/client/beefcake/object_methods"
26
+ true
27
+ rescue LoadError, NameError
28
+ false
29
+ end
30
+ end
31
+
32
+ def set_client_id(id)
33
+ value = case id
34
+ when Integer
35
+ [id].pack("N")
36
+ else
37
+ id.to_s
38
+ end
39
+ req = RpbSetClientIdReq.new(:client_id => value)
40
+ write_protobuff(:SetClientIdReq, req)
41
+ decode_response
42
+ end
43
+
44
+ def fetch_object(bucket, key, r=nil)
45
+ bucket = Bucket === bucket ? bucket.name : bucket
46
+ req = RpbGetReq.new(:bucket => bucket, :key => key)
47
+ req.r = normalize_quorum_value(r) if r
48
+ write_protobuff(:GetReq, req)
49
+ decode_response(RObject.new(client.bucket(bucket), key))
50
+ end
51
+
52
+ def reload_object(robject, r=nil)
53
+ req = RpbGetReq.new(:bucket => robject.bucket.name, :key => robject.key)
54
+ req.r = normalize_quorum_value(r) if r
55
+ write_protobuff(:GetReq, req)
56
+ decode_response(robject)
57
+ end
58
+
59
+ def store_object(robject, returnbody=false, w=nil, dw=nil)
60
+ if robject.prevent_stale_writes
61
+ other = fetch_object(robject.bucket, robject.key)
62
+ raise Riak::ProtobuffsFailedRequest(:stale_object, t("stale_write_prevented")) unless other.vclock == robject.vclock
63
+ end
64
+ req = dump_object(robject)
65
+ req.w = normalize_quorum_value(w) if w
66
+ req.dw = normalize_quorum_value(dw) if dw
67
+ req.return_body = returnbody
68
+ write_protobuff(:PutReq, req)
69
+ decode_response(robject)
70
+ end
71
+
72
+ def delete_object(bucket, key, rw=nil)
73
+ bucket = Bucket === bucket ? bucket.name : bucket
74
+ req = RpbDelReq.new(:bucket => bucket, :key => key)
75
+ req.rw = normalize_quorum_value(rw) if rw
76
+ write_protobuff(:DelReq, req)
77
+ decode_response
78
+ end
79
+
80
+ def get_bucket_props(bucket)
81
+ bucket = bucket.name if Bucket === bucket
82
+ req = RpbGetBucketReq.new(:bucket => bucket)
83
+ write_protobuff(:GetBucketReq, req)
84
+ decode_response
85
+ end
86
+
87
+ def set_bucket_props(bucket, props)
88
+ bucket = bucket.name if Bucket === bucket
89
+ props = props.slice('n_val', 'allow_mult')
90
+ req = RpbSetBucketReq.new(:bucket => bucket, :props => RpbBucketProps.new(props))
91
+ write_protobuff(:SetBucketReq, req)
92
+ decode_response
93
+ end
94
+
95
+ def list_keys(bucket, &block)
96
+ bucket = bucket.name if Bucket === bucket
97
+ req = RpbListKeysReq.new(:bucket => bucket)
98
+ write_protobuff(:ListKeysReq, req)
99
+ keys = []
100
+ pump = Pump.new(block) if block_given?
101
+ while msg = decode_response
102
+ break if msg.done
103
+ if pump
104
+ pump.pump msg.keys
105
+ else
106
+ keys += msg.keys
107
+ end
108
+ end
109
+ block_given? || keys
110
+ end
111
+
112
+ def mapred(mr, &block)
113
+ req = RpbMapRedReq.new(:request => mr.to_json, :content_type => "application/json")
114
+ write_protobuff(:MapRedReq, req)
115
+ results = []
116
+ pump = Pump.new(lambda do |msg|
117
+ block.call msg.phase, JSON.parse(msg.response)
118
+ end) if block_given?
119
+ while msg = decode_response
120
+ break if msg.done
121
+ if pump
122
+ pump.pump msg
123
+ else
124
+ results[msg.phase] ||= []
125
+ results[msg.phase] += JSON.parse(msg.response)
126
+ end
127
+ end
128
+ block_given? || results.size == 1 ? results.first : results
129
+ end
130
+
131
+ private
132
+ def write_protobuff(code, message)
133
+ encoded = message.encode
134
+ socket.write([encoded.length+1, MESSAGE_CODES.index(code)].pack("NC"))
135
+ socket.write(encoded)
136
+ end
137
+
138
+ def decode_response(*args)
139
+ header = socket.read(5)
140
+ raise SocketError, "Unexpected EOF on PBC socket" if header.nil?
141
+ msglen, msgcode = header.unpack("NC")
142
+ if msglen == 1
143
+ case MESSAGE_CODES[msgcode]
144
+ when :PingResp, :SetClientIdResp, :PutResp, :DelResp, :SetBucketResp
145
+ true
146
+ when :ListBucketsResp, :ListKeysResp
147
+ []
148
+ when :GetResp
149
+ raise Riak::ProtobuffsFailedRequest.new(:not_found, t('not_found'))
150
+ else
151
+ false
152
+ end
153
+ else
154
+ message = socket.read(msglen-1)
155
+ case MESSAGE_CODES[msgcode]
156
+ when :ErrorResp
157
+ res = RpbErrorResp.decode(message)
158
+ raise Riak::ProtobuffsFailedRequest.new(res.errcode, res.errmsg)
159
+ when :GetClientIdResp
160
+ res = RpbGetClientIdResp.decode(message)
161
+ res.client_id
162
+ when :GetServerInfoResp
163
+ res = RpbGetServerInfoResp.decode(message)
164
+ {:node => res.node, :server_version => res.server_version}
165
+ when :GetResp, :PutResp
166
+ res = RpbGetResp.decode(message)
167
+ load_object(res, args.first)
168
+ when :ListBucketsResp
169
+ res = RpbListBucketsResp.decode(message)
170
+ res.buckets
171
+ when :ListKeysResp
172
+ RpbListKeysResp.decode(message)
173
+ when :GetBucketResp
174
+ res = RpbGetBucketResp.decode(message)
175
+ {'n_val' => res.props.n_val, 'allow_mult' => res.props.allow_mult}
176
+ when :MapRedResp
177
+ RpbMapRedResp.decode(message)
178
+ end
179
+ end
180
+ rescue SocketError => e
181
+ reset_socket
182
+ raise Riak::ProtobuffsFailedRequest.new(:server_error, e.message)
183
+ end
184
+ end
185
+ end
186
+ end
@@ -12,12 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  require 'riak'
15
-
16
- begin
17
- require 'fiber'
18
- rescue LoadError
19
- require 'riak/util/fiber1.8'
20
- end
15
+ require 'riak/client/pump'
21
16
 
22
17
  module Riak
23
18
  class Client
@@ -34,20 +29,13 @@ module Riak
34
29
  end
35
30
 
36
31
  private
37
- def perform(method, uri, headers, expect, data=nil)
32
+ def perform(method, uri, headers, expect, data=nil, &block)
38
33
  # Setup
39
34
  curl.headers = RequestHeaders.new(headers).to_a
40
35
  curl.url = uri.to_s
41
36
  response_headers.initialize_http_header(nil)
42
37
  if block_given?
43
- _curl = curl
44
- Fiber.new {
45
- f = Fiber.current
46
- _curl.on_body {|chunk| f.resume(chunk); chunk.size }
47
- loop do
48
- yield Fiber.yield
49
- end
50
- }.resume
38
+ curl.on_body(&Pump.new(block))
51
39
  else
52
40
  curl.on_body
53
41
  end
@@ -74,12 +62,14 @@ module Riak
74
62
  end
75
63
  result
76
64
  else
77
- raise FailedRequest.new(method, expect, curl.response_code, response_headers.to_hash, curl.body_str)
65
+ raise HTTPFailedRequest.new(method, expect, curl.response_code, response_headers.to_hash, curl.body_str)
78
66
  end
79
67
  end
80
68
 
81
69
  def curl
82
70
  Thread.current[:curl_easy_handle] ||= Curl::Easy.new.tap do |c|
71
+ configure_ssl(c) if @client.ssl_enabled?
72
+
83
73
  c.follow_location = false
84
74
  c.on_header do |header_line|
85
75
  response_headers.parse(header_line)
@@ -87,6 +77,10 @@ module Riak
87
77
  end
88
78
  end
89
79
  end
80
+
81
+ def configure_ssl(curl)
82
+ curl.ssl_verify_peer = @client.ssl_options[:verify_mode] == "peer"
83
+ end
90
84
  end
91
85
  end
92
86
  end
@@ -12,22 +12,18 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  require 'riak'
15
+ require 'riak/client/pump'
15
16
 
16
17
  module Riak
17
18
  class Client
18
19
  # An HTTP backend for Riak::Client that uses Wesley Beary's Excon
19
- # HTTP library. Comforms to the Riak::Client::HTTPBackend
20
+ # HTTP library. Conforms to the Riak::Client::HTTPBackend
20
21
  # interface.
21
22
  class ExconBackend < HTTPBackend
22
23
  def self.configured?
23
24
  begin
24
- begin
25
- require 'fiber'
26
- rescue LoadError
27
- require 'riak/util/fiber1.8'
28
- end
29
25
  require 'excon'
30
- Excon::VERSION >= "0.3.4"
26
+ Excon::VERSION >= "0.5.7"
31
27
  rescue LoadError
32
28
  false
33
29
  end
@@ -35,6 +31,8 @@ module Riak
35
31
 
36
32
  private
37
33
  def perform(method, uri, headers, expect, data=nil, &block)
34
+ configure_ssl if @client.ssl_enabled?
35
+
38
36
  params = {
39
37
  :method => method.to_s.upcase,
40
38
  :headers => RequestHeaders.new(headers).to_hash,
@@ -43,16 +41,9 @@ module Riak
43
41
  params[:query] = uri.query if uri.query
44
42
  params[:body] = data if [:put,:post].include?(method)
45
43
  params[:idempotent] = (method != :post)
46
- if block_given?
47
- passed_block = block
48
- Fiber.new {
49
- f = Fiber.current
50
- block = lambda {|chunk| f.resume(chunk) }
51
- loop do
52
- passed_block.call Fiber.yield
53
- end
54
- }.resume
55
- end
44
+
45
+ block = Pump.new(block) if block_given?
46
+
56
47
  response = connection.request(params, &block)
57
48
  if valid_response?(expect, response.status)
58
49
  response_headers.initialize_http_header(response.headers)
@@ -62,13 +53,18 @@ module Riak
62
53
  end
63
54
  result
64
55
  else
65
- raise FailedRequest.new(method, expect, response.status, response.headers, response.body)
56
+ raise HTTPFailedRequest.new(method, expect, response.status, response.headers, response.body)
66
57
  end
67
58
  end
68
59
 
69
60
  def connection
70
61
  @connection ||= Excon::Connection.new(root_uri.to_s)
71
62
  end
63
+
64
+ def configure_ssl
65
+ Excon.ssl_verify_peer = @client.ssl_options[:verify_mode].to_s === "peer"
66
+ Excon.ssl_ca_path = @client.ssl_options[:ca_path] if @client.ssl_options[:ca_path]
67
+ end
72
68
  end
73
69
  end
74
70
  end
@@ -164,20 +164,20 @@ module Riak
164
164
  # @return [Array<Object>] the list of results, if no block was
165
165
  # given
166
166
  def mapred(mr)
167
- response = post(200, riak_kv_wm_mapred, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"})
168
- data = begin
169
- JSON.parse(response[:body])
170
- rescue
171
- response
172
- end
173
- # This fakes streaming until the streaming MIME parser works.
174
167
  if block_given?
175
- data = [data] if mr.query.count {|p| p.keep } == 1
176
- data.each_with_index do |phase, idx|
177
- phase.each {|obj| yield idx, obj }
168
+ parser = Riak::Util::Multipart::StreamParser.new do |response|
169
+ result = JSON.parse(response[:body])
170
+ yield result['phase'], result['data']
178
171
  end
172
+ post(200, riak_kv_wm_mapred, {:chunked => true}, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"}, &parser)
173
+ nil
179
174
  else
180
- data
175
+ response = post(200, riak_kv_wm_mapred, mr.to_json, {"Content-Type" => "application/json", "Accept" => "application/json"})
176
+ begin
177
+ JSON.parse(response[:body])
178
+ rescue
179
+ response
180
+ end
181
181
  end
182
182
  end
183
183
 
@@ -200,8 +200,8 @@ module Riak
200
200
  Util::Multipart.parse(response[:body], boundary).map do |group|
201
201
  group.map do |obj|
202
202
  if obj[:headers] && obj[:body] && obj[:headers]['location']
203
- bucket, key = $1, $2 if obj[:headers]['location'].first =~ %r{/.*/(.*)/(.*)$}
204
- load_object(RObject.new(client.bucket(bucket), key), obj)
203
+ bucket = $1 if obj[:headers]['location'].first =~ %r{/.*/(.*)/.*$}
204
+ load_object(RObject.new(client.bucket(bucket), nil), obj)
205
205
  end
206
206
  end
207
207
  end