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