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
@@ -104,7 +104,7 @@ describe Riak::MapReduce do
104
104
 
105
105
  it "should accept a list of key-filters along with a bucket" do
106
106
  @mr.add("foo", [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]])
107
- @mr.inputs.should == {:bucket => "foo", :filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
107
+ @mr.inputs.should == {:bucket => "foo", :key_filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
108
108
  end
109
109
 
110
110
  it "should add a bucket and filter list via a builder block" do
@@ -113,7 +113,7 @@ describe Riak::MapReduce do
113
113
  string_to_int
114
114
  between 2009, 2010
115
115
  end
116
- @mr.inputs.should == {:bucket => "foo", :filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
116
+ @mr.inputs.should == {:bucket => "foo", :key_filters => [[:tokenize, "-", 3], [:string_to_int], [:between, 2009, 2010]]}
117
117
  end
118
118
  end
119
119
 
@@ -250,19 +250,19 @@ describe Riak::MapReduce do
250
250
  end
251
251
 
252
252
  it "should interpret failed requests with JSON content-types as map reduce errors" do
253
- @backend.stub!(:mapred).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["application/json"]}, '{"error":"syntax error"}'))
253
+ @backend.stub!(:mapred).and_raise(Riak::HTTPFailedRequest.new(:post, 200, 500, {"content-type" => ["application/json"]}, '{"error":"syntax error"}'))
254
254
  lambda { @mr.run }.should raise_error(Riak::MapReduceError)
255
255
  begin
256
256
  @mr.run
257
257
  rescue Riak::MapReduceError => mre
258
- mre.message.should == '{"error":"syntax error"}'
258
+ mre.message.should include('{"error":"syntax error"}')
259
259
  else
260
260
  fail "No exception raised!"
261
261
  end
262
262
  end
263
263
 
264
264
  it "should re-raise non-JSON error responses" do
265
- @backend.stub!(:mapred).and_raise(Riak::FailedRequest.new(:post, 200, 500, {"content-type" => ["text/plain"]}, 'Oops, you bwoke it.'))
265
+ @backend.stub!(:mapred).and_raise(Riak::HTTPFailedRequest.new(:post, 200, 500, {"content-type" => ["text/plain"]}, 'Oops, you bwoke it.'))
266
266
  lambda { @mr.run }.should raise_error(Riak::FailedRequest)
267
267
  end
268
268
  end
@@ -25,4 +25,5 @@ describe Riak::Client::NetHTTPBackend do
25
25
  end
26
26
 
27
27
  it_should_behave_like "HTTP backend"
28
+
28
29
  end
@@ -374,7 +374,7 @@ describe Riak::RObject do
374
374
  end
375
375
 
376
376
  it "should pass through a failed request exception" do
377
- @backend.should_receive(:delete_object).and_raise(Riak::FailedRequest.new(:delete, [204,404], 500, {}, ""))
377
+ @backend.should_receive(:delete_object).and_raise(Riak::HTTPFailedRequest.new(:delete, [204,404], 500, {}, ""))
378
378
  lambda { @object.delete }.should raise_error(Riak::FailedRequest)
379
379
  end
380
380
  end
@@ -0,0 +1,66 @@
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 File.expand_path("../spec_helper", File.dirname(__FILE__))
15
+
16
+ describe Riak::Util::Multipart::StreamParser do
17
+ let(:klass) { Riak::Util::Multipart::StreamParser }
18
+ let(:block) { mock }
19
+ it "should detect the initial boundary" do
20
+ text = "--boundary1\r\nContent-Type: text/plain\r\n\r\nfoo\r\n--boundary1--\r\n"
21
+ parser = klass.new do |result|
22
+ result[:headers]['content-type'].should include("text/plain")
23
+ result[:body].should == "foo"
24
+ end
25
+ parser.accept text
26
+ end
27
+
28
+ it "should detect inner multipart bodies" do
29
+ block.should_receive(:ping).once.and_return(true)
30
+ parser = klass.new do |result|
31
+ block.ping
32
+ result.should have(1).item
33
+ result.first[:headers]['content-type'].should include("text/plain")
34
+ result.first[:body].should == "SCP sloooow...."
35
+ end
36
+ File.open("spec/fixtures/multipart-with-body.txt", "r") do |f|
37
+ while chunk = f.read(16)
38
+ parser.accept chunk
39
+ end
40
+ end
41
+ end
42
+
43
+ it "should yield successive complete chunks to the block" do
44
+ block.should_receive(:ping).twice.and_return(true)
45
+ parser = klass.new do |result|
46
+ block.ping
47
+ result[:headers]['content-type'].should include("application/json")
48
+ lambda { JSON.parse(result[:body]) }.should_not raise_error
49
+ end
50
+ File.open("spec/fixtures/multipart-mapreduce.txt", "r") do |f|
51
+ while chunk = f.read(16)
52
+ parser.accept chunk
53
+ end
54
+ end
55
+ end
56
+
57
+ it "should yield successive complete bodies to the block, even when multiple bodies are accepted in a single chunk" do
58
+ block.should_receive(:ping).twice.and_return(true)
59
+ parser = klass.new do |result|
60
+ block.ping
61
+ result[:headers]['content-type'].should include("application/json")
62
+ lambda { JSON.parse(result[:body]) }.should_not raise_error
63
+ end
64
+ parser.accept File.read("spec/fixtures/multipart-mapreduce.txt")
65
+ end
66
+ end
@@ -7,7 +7,7 @@ module DrbMockServer
7
7
  def start_client
8
8
  # JRuby doesn't support fork
9
9
  if defined? JRUBY_VERSION
10
- @server = MockServer.new
10
+ @server = MockServer.new(2)
11
11
  at_exit { @server.stop }
12
12
  else
13
13
  child_pid = Process.fork do
@@ -17,6 +17,7 @@ module DrbMockServer
17
17
  at_exit { Process.kill("HUP", child_pid); Process.wait2 }
18
18
  DRb.start_service
19
19
  @server = DRbObject.new_with_uri(DRBURI)
20
+ sleep 1
20
21
  end
21
22
  true
22
23
  end
@@ -29,7 +30,6 @@ module DrbMockServer
29
30
  @server.send(meth, *args, &block)
30
31
  end
31
32
 
32
- private
33
33
  def start_server
34
34
  server = MockServer.new
35
35
  DRb.start_service(DRBURI, server)
@@ -212,4 +212,31 @@ shared_examples_for "HTTP backend" do
212
212
  end
213
213
  end
214
214
  end
215
+
216
+ describe "SSL" do
217
+ it "should be supported" do
218
+ @client.port = $mock_server.port + 1 unless @client.http_backend == :NetHTTP
219
+ @client.ssl = true
220
+ setup_http_mock(:get, @backend.path("/riak/","ssl").to_s, :body => "Success!")
221
+ response = @backend.get(200, "/riak/","ssl")
222
+ response[:code].should == 200
223
+ end
224
+ end
225
+
226
+ describe "HTTP Basic Authentication", :basic_auth => true do
227
+ it "should add the http basic auth header" do
228
+ @client.basic_auth = "ripple:rocks"
229
+ if @client.http_backend == :NetHTTP
230
+ setup_http_mock(:get, "http://ripple:rocks@127.0.0.1:8098/riak/auth", :body => 'Success!')
231
+ else
232
+ @_mock_set = "Basic #{Base64::encode64("ripple:rocks").strip}"
233
+ $mock_server.attach do |env|
234
+ $mock_server.satisfied = env['HTTP_AUTHORIZATION'] == @_mock_set
235
+ [200, {}, Array('Success!')]
236
+ end
237
+ end
238
+ response = @backend.get(200, "/riak/", "auth")
239
+ response[:code].should == 200
240
+ end
241
+ end
215
242
  end
@@ -16,6 +16,9 @@
16
16
  # Based on code from Rob Styles and Chris Tierney found at:
17
17
  # http://dynamicorange.com/2009/02/18/ruby-mock-web-server/
18
18
  require 'rack'
19
+ require 'openssl'
20
+ require 'webrick/https'
21
+ require 'rack/handler/webrick'
19
22
 
20
23
  class MockServer
21
24
  attr_accessor :port
@@ -25,14 +28,24 @@ class MockServer
25
28
  self.port = 4000 + rand(61535)
26
29
  @block = nil
27
30
  @parent_thread = Thread.current
31
+ options = {:AccessLog => [], :Logger => NullLogger.new, :Host => '127.0.0.1'}
28
32
  @thread = Thread.new do
29
- Rack::Handler::WEBrick.run(self, :Port => port, :AccessLog => [], :Logger => NullLogger.new, :Host => '127.0.0.1')
33
+ Rack::Handler::WEBrick.run(self, options.merge(:Port => port))
34
+ end
35
+ @ssl_thread = Thread.new do
36
+ Rack::Handler::WEBrick.run(self, options.merge(:Port => port+1,
37
+ :SSLEnable => true,
38
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
39
+ :SSLCertificate => read_cert,
40
+ :SSLPrivateKey => read_pkey,
41
+ :SSLCertName => [ [ "CN",'127.0.0.1' ] ]))
30
42
  end
31
43
  sleep pause # give the server time to fire up… YUK!
32
44
  end
33
45
 
34
46
  def stop
35
47
  Thread.kill(@thread)
48
+ Thread.kill(@ssl_thread)
36
49
  end
37
50
 
38
51
  def expect(status, headers, method, path, query, body)
@@ -64,6 +77,14 @@ class MockServer
64
77
  end
65
78
  end
66
79
 
80
+ def read_pkey
81
+ OpenSSL::PKey::RSA.new(File.read(File.expand_path(File.dirname(__FILE__) + '/../fixtures/server.cert.key')), 'ripple')
82
+ end
83
+
84
+ def read_cert
85
+ OpenSSL::X509::Certificate.new(File.read((File.expand_path(File.dirname(__FILE__) + '/../fixtures/server.cert.crt'))))
86
+ end
87
+
67
88
  class NullLogger
68
89
  def fatal(msg) end
69
90
  def error(msg) end
@@ -0,0 +1,255 @@
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
+
15
+ shared_examples_for "Unified backend API" do
16
+ # ping
17
+ it "should ping the server" do
18
+ @backend.ping.should be_true
19
+ end
20
+
21
+ # fetch_object
22
+ context "fetching an object" do
23
+ before do
24
+ @robject = Riak::RObject.new(@client.bucket("test"), "fetch")
25
+ @robject.content_type = "application/json"
26
+ @robject.data = { "test" => "pass" }
27
+ @backend.store_object(@robject)
28
+ end
29
+
30
+ it "should find a stored object" do
31
+ robj = @backend.fetch_object("test", "fetch")
32
+ robj.should be_kind_of(Riak::RObject)
33
+ robj.data.should == { "test" => "pass" }
34
+ end
35
+
36
+ it "should raise an error when the object is not found" do
37
+ begin
38
+ @backend.fetch_object("test", "notfound")
39
+ rescue Riak::FailedRequest => exception
40
+ @exception = exception
41
+ end
42
+ @exception.should be_kind_of(Riak::FailedRequest)
43
+ @exception.should be_not_found
44
+ end
45
+
46
+ [1,2,3,:one,:quorum,:all,:default].each do |q|
47
+ it "should accept a R value of #{q.inspect} for the request" do
48
+ robj = @backend.fetch_object("test", "fetch", q)
49
+ robj.should be_kind_of(Riak::RObject)
50
+ robj.data.should == { "test" => "pass" }
51
+ end
52
+ end
53
+ end
54
+
55
+ # reload_object
56
+ context "reloading an existing object" do
57
+ before do
58
+ @robject = Riak::RObject.new(@client.bucket('test'), 'reload')
59
+ @robject.content_type = "application/json"
60
+ @robject.data = {"test" => "pass"}
61
+ @backend.store_object(@robject)
62
+ @robject2 = @backend.fetch_object("test", "reload")
63
+ @robject2.data["test"] = "second"
64
+ @backend.store_object(@robject2, true)
65
+ end
66
+
67
+ it "should modify the object with the reloaded data" do
68
+ @backend.reload_object(@robject)
69
+ end
70
+
71
+ [1,2,3,:one,:quorum,:all,:default].each do |q|
72
+ it "should accept a valid R value of #{q.inspect} for the request" do
73
+ @backend.reload_object(@robject, q)
74
+ end
75
+ end
76
+
77
+ after do
78
+ @robject.vclock.should == @robject2.vclock
79
+ @robject.data['test'].should == "second"
80
+ end
81
+ end
82
+
83
+ # store_object
84
+ context "storing an object" do
85
+ before do
86
+ @robject = Riak::RObject.new(@client.bucket('test'), 'store')
87
+ @robject.content_type = "application/json"
88
+ @robject.data = {"test" => "pass"}
89
+ end
90
+
91
+ it "should save the object" do
92
+ @backend.store_object(@robject)
93
+ end
94
+
95
+ it "should modify the object with the returned data if returnbody" do
96
+ @backend.store_object(@robject, true)
97
+ @robject.vclock.should be_present
98
+ end
99
+
100
+ [1,2,3,:one,:quorum,:all,:default].each do |q|
101
+ it "should accept a W value of #{q.inspect} for the request" do
102
+ @backend.store_object(@robject, false, q)
103
+ @client.bucket("test").exists?("store").should be_true
104
+ end
105
+
106
+ it "should accept a DW value of #{q.inspect} for the request" do
107
+ @backend.store_object(@robject, false, nil, q)
108
+ end
109
+ end
110
+
111
+ after do
112
+ @client.bucket("test").exists?("store").should be_true
113
+ end
114
+ end
115
+
116
+ # delete_object
117
+ context "deleting an object" do
118
+ before do
119
+ @obj = Riak::RObject.new(@client.bucket("test"), "delete")
120
+ @obj.content_type = "application/json"
121
+ @obj.data = [1]
122
+ @backend.store_object(@obj)
123
+ end
124
+
125
+ it "should remove the object" do
126
+ @backend.delete_object("test", "delete")
127
+ @obj.bucket.exists?("delete").should be_false
128
+ end
129
+
130
+ [1,2,3,:one,:quorum,:all,:default].each do |q|
131
+ it "should accept an RW value of #{q.inspect} for the request" do
132
+ @backend.delete_object("test", "delete", q)
133
+ end
134
+ end
135
+
136
+ after do
137
+ @obj.bucket.exists?("delete").should be_false
138
+ end
139
+ end
140
+
141
+ # get_bucket_props
142
+ context "fetching bucket properties" do
143
+ it "should fetch a hash of bucket properties" do
144
+ props = @backend.get_bucket_props("test")
145
+ props.should be_kind_of(Hash)
146
+ props.should include("n_val")
147
+ end
148
+ end
149
+
150
+ # set_bucket_props
151
+ context "setting bucket properties" do
152
+ it "should store properties for the bucket" do
153
+ @backend.set_bucket_props("test", {"n_val" => 3})
154
+ @backend.get_bucket_props("test")["n_val"].should == 3
155
+ end
156
+ end
157
+
158
+ # list_keys
159
+ context "listing keys in a bucket" do
160
+ before do
161
+ obj = Riak::RObject.new(@client.bucket("test"), "keys")
162
+ obj.content_type = "application/json"
163
+ obj.data = [1]
164
+ @backend.store_object(obj)
165
+ end
166
+
167
+ it "should fetch an array of string keys" do
168
+ @backend.list_keys("test").should == ["keys"]
169
+ end
170
+
171
+ context "streaming through a block" do
172
+ it "should pass an array of keys to the block" do
173
+ @backend.list_keys("test") do |keys|
174
+ keys.should == ["keys"] unless keys.empty?
175
+ end
176
+ end
177
+
178
+ it "should allow requests issued inside the block to execute" do
179
+ errors = []
180
+ @backend.list_keys("test") do |keys|
181
+ keys.each do |key|
182
+ begin
183
+ @backend.fetch_object("test", key)
184
+ rescue => e
185
+ errors << e
186
+ end
187
+ end
188
+ end
189
+ errors.should be_empty
190
+ end
191
+ end
192
+ end
193
+
194
+ # list_buckets
195
+ context "listing buckets" do
196
+ before do
197
+ obj = Riak::RObject.new(@client.bucket("test"), "buckets")
198
+ obj.content_type = "application/json"
199
+ obj.data = [1]
200
+ @backend.store_object(obj)
201
+ end
202
+
203
+ it "should fetch a list of string bucket names" do
204
+ list = @backend.list_buckets
205
+ list.should be_kind_of(Array)
206
+ list.should include("test")
207
+ end
208
+ end
209
+
210
+ # mapred
211
+ context "performing MapReduce" do
212
+ before do
213
+ obj = Riak::RObject.new(@client.bucket("test"), "1")
214
+ obj.content_type = "application/json"
215
+ obj.data = {"value" => "1" }
216
+ @backend.store_object(obj)
217
+ @mapred = Riak::MapReduce.new(@client).add("test").map("Riak.mapValuesJson", :keep => true)
218
+ end
219
+
220
+ it "should perform a simple MapReduce request" do
221
+ @backend.mapred(@mapred).should == [{"value" => "1"}]
222
+ end
223
+
224
+ context "streaming results through a block" do
225
+ it "should pass phase number and result to the block" do
226
+ @backend.mapred(@mapred) do |phase, result|
227
+ unless result.empty?
228
+ phase.should == 0
229
+ result.should == [{"value" => "1"}]
230
+ end
231
+ end
232
+ end
233
+
234
+ it "should allow requests issued inside the block to execute" do
235
+ errors = []
236
+ @backend.mapred(@mapred) do |phase, result|
237
+ unless result.empty?
238
+ result.each do |v|
239
+ begin
240
+ @backend.fetch_object("test", v['value'])
241
+ rescue => e
242
+ errors << e
243
+ end
244
+ end
245
+ end
246
+ end
247
+ errors.should be_empty
248
+ end
249
+ end
250
+ end
251
+
252
+ after do
253
+ $test_server.recycle if $test_server.started?
254
+ end
255
+ end