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