riak-client 1.2.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/Gemfile +1 -7
  4. data/README.markdown +66 -0
  5. data/RELEASE_NOTES.md +27 -0
  6. data/lib/riak/bucket.rb +24 -5
  7. data/lib/riak/client.rb +42 -7
  8. data/lib/riak/client/beefcake/message_codes.rb +56 -0
  9. data/lib/riak/client/beefcake/messages.rb +190 -18
  10. data/lib/riak/client/beefcake_protobuffs_backend.rb +143 -10
  11. data/lib/riak/client/feature_detection.rb +26 -1
  12. data/lib/riak/client/http_backend.rb +58 -9
  13. data/lib/riak/client/http_backend/bucket_streamer.rb +15 -0
  14. data/lib/riak/client/http_backend/chunked_json_streamer.rb +42 -0
  15. data/lib/riak/client/http_backend/configuration.rb +17 -1
  16. data/lib/riak/client/http_backend/key_streamer.rb +4 -32
  17. data/lib/riak/client/protobuffs_backend.rb +12 -34
  18. data/lib/riak/counter.rb +101 -0
  19. data/lib/riak/index_collection.rb +71 -0
  20. data/lib/riak/list_buckets.rb +28 -0
  21. data/lib/riak/locale/en.yml +14 -0
  22. data/lib/riak/multiget.rb +123 -0
  23. data/lib/riak/node.rb +2 -0
  24. data/lib/riak/node/configuration.rb +32 -21
  25. data/lib/riak/node/defaults.rb +2 -0
  26. data/lib/riak/node/generation.rb +19 -7
  27. data/lib/riak/node/version.rb +2 -16
  28. data/lib/riak/robject.rb +1 -0
  29. data/lib/riak/secondary_index.rb +67 -0
  30. data/lib/riak/version.rb +1 -1
  31. data/riak-client.gemspec +3 -2
  32. data/spec/integration/riak/counters_spec.rb +51 -0
  33. data/spec/integration/riak/http_backends_spec.rb +24 -14
  34. data/spec/integration/riak/node_spec.rb +6 -28
  35. data/spec/riak/beefcake_protobuffs_backend_spec.rb +84 -0
  36. data/spec/riak/bucket_spec.rb +55 -5
  37. data/spec/riak/client_spec.rb +34 -0
  38. data/spec/riak/counter_spec.rb +122 -0
  39. data/spec/riak/index_collection_spec.rb +50 -0
  40. data/spec/riak/list_buckets_spec.rb +41 -0
  41. data/spec/riak/multiget_spec.rb +76 -0
  42. data/spec/riak/robject_spec.rb +4 -1
  43. data/spec/riak/secondary_index_spec.rb +225 -0
  44. data/spec/spec_helper.rb +1 -0
  45. data/spec/support/sometimes.rb +2 -2
  46. data/spec/support/unified_backend_examples.rb +4 -0
  47. metadata +41 -47
@@ -37,6 +37,90 @@ describe Riak::Client::BeefcakeProtobuffsBackend do
37
37
  backend.list_keys(exp_bucket).should == exp_keys
38
38
  end
39
39
 
40
+ context "secondary index" do
41
+ before :each do
42
+ @socket = mock(:socket).as_null_object
43
+ TCPSocket.stub(:new => @socket)
44
+ end
45
+ context 'when streaming' do
46
+ it "should stream when a block is given" do
47
+ backend.should_receive(:write_protobuff) do |msg, req|
48
+ msg.should == :IndexReq
49
+ req[:stream].should == true
50
+ end
51
+ backend.should_receive(:decode_index_response)
52
+
53
+ blk = proc{:asdf}
54
+
55
+ backend.get_index('bucket', 'words', 'asdf'..'hjkl', &blk)
56
+ end
57
+
58
+ it "should send batches of results to the block" do
59
+ backend.should_receive(:write_protobuff)
60
+
61
+ response_sets = [%w{asdf asdg asdh}, %w{gggg gggh gggi}]
62
+ response_messages = response_sets.map do |s|
63
+ Riak::Client::BeefcakeProtobuffsBackend::RpbIndexResp.new keys: s
64
+ end
65
+ response_messages.last.done = true
66
+
67
+ response_chunks = response_messages.map do |m|
68
+ encoded = m.encode
69
+ header = [encoded.length + 1, 26].pack 'NC'
70
+ [header, encoded]
71
+ end.flatten
72
+
73
+ @socket.should_receive(:read).and_return(*response_chunks)
74
+
75
+ block_body = mock 'block'
76
+ block_body.should_receive(:check).with(response_sets.first).once
77
+ block_body.should_receive(:check).with(response_sets.last).once
78
+
79
+ blk = proc {|m| block_body.check m }
80
+
81
+ backend.get_index 'bucket', 'words', 'asdf'..'hjkl', &blk
82
+ end
83
+ end
84
+
85
+ it "should return a full batch of results when not streaming" do
86
+ backend.should_receive(:write_protobuff) do |msg, req|
87
+ msg.should == :IndexReq
88
+ req[:stream].should_not be
89
+ end
90
+
91
+ response_message = Riak::Client::BeefcakeProtobuffsBackend::
92
+ RpbIndexResp.new(
93
+ keys: %w{asdf asdg asdh}
94
+ ).encode
95
+ header = [response_message.length + 1, 26].pack 'NC'
96
+ @socket.should_receive(:read).and_return(header, response_message)
97
+
98
+ results = backend.get_index 'bucket', 'words', 'asdf'..'hjkl'
99
+ results.should == %w{asdf asdg asdh}
100
+ end
101
+
102
+ it "should not crash out when no keys or terms are released" do
103
+ backend.should_receive(:write_protobuff) do |msg, req|
104
+ msg.should == :IndexReq
105
+ req[:stream].should_not be
106
+ end
107
+
108
+ response_message = Riak::Client::BeefcakeProtobuffsBackend::
109
+ RpbIndexResp.new().encode
110
+
111
+ header = [response_message.length + 1, 26].pack 'NC'
112
+ @socket.should_receive(:read).and_return(header, response_message)
113
+
114
+ results = nil
115
+ fetch = proc do
116
+ results = backend.get_index 'bucket', 'words', 'asdf'
117
+ end
118
+
119
+ fetch.should_not raise_error
120
+ results.should == []
121
+ end
122
+ end
123
+
40
124
  context "#mapred" do
41
125
  let(:mapred) { Riak::MapReduce.new(client).add('test').map("function(){}").map("function(){}") }
42
126
 
@@ -16,6 +16,7 @@ describe Riak::Bucket do
16
16
  lambda { Riak::Bucket.new("foo") }.should raise_error
17
17
  lambda { Riak::Bucket.new("foo", @client) }.should raise_error
18
18
  lambda { Riak::Bucket.new(@client, "foo") }.should_not raise_error
19
+ expect { Riak::Bucket.new(@client, '') }.to raise_error(ArgumentError)
19
20
  end
20
21
 
21
22
  it "should set the client and name attributes" do
@@ -27,12 +28,12 @@ describe Riak::Bucket do
27
28
 
28
29
  describe "accessing keys" do
29
30
  it "should list the keys" do
30
- @backend.should_receive(:list_keys).with(@bucket).and_return(["bar"])
31
+ @backend.should_receive(:list_keys).with(@bucket, {}).and_return(["bar"])
31
32
  @bucket.keys.should == ["bar"]
32
33
  end
33
34
 
34
35
  it "should allow streaming keys through block" do
35
- @backend.should_receive(:list_keys).with(@bucket).and_yield([]).and_yield(["bar"]).and_yield(["baz"])
36
+ @backend.should_receive(:list_keys).with(@bucket, {}).and_yield([]).and_yield(["bar"]).and_yield(["baz"])
36
37
  all_keys = []
37
38
  @bucket.keys do |list|
38
39
  all_keys.concat(list)
@@ -41,7 +42,7 @@ describe Riak::Bucket do
41
42
  end
42
43
 
43
44
  it "should not cache the list of keys" do
44
- @backend.should_receive(:list_keys).with(@bucket).twice.and_return(["bar"])
45
+ @backend.should_receive(:list_keys).with(@bucket, {}).twice.and_return(["bar"])
45
46
  2.times { @bucket.keys.should == ['bar'] }
46
47
  end
47
48
 
@@ -52,6 +53,24 @@ describe Riak::Bucket do
52
53
  @bucket.keys
53
54
  Riak.disable_list_keys_warnings = true
54
55
  end
56
+
57
+ it "should allow a specified timeout when listing keys" do
58
+ @backend.should_receive(:list_keys).with(@bucket, timeout: 1234).and_return(%w{bar})
59
+
60
+ keys = @bucket.keys timeout: 1234
61
+
62
+ keys.should == %w{bar}
63
+ end
64
+ end
65
+
66
+ describe "accessing a counter" do
67
+ it "should return a counter object" do
68
+ Riak::Counter.should_receive(:new).with(@bucket, 'asdf').and_return('example counter')
69
+
70
+ new_counter = @bucket.counter 'asdf'
71
+
72
+ new_counter.should == 'example counter'
73
+ end
55
74
  end
56
75
 
57
76
  describe "setting the bucket properties" do
@@ -104,6 +123,12 @@ describe Riak::Bucket do
104
123
  @backend.should_receive(:fetch_object).with(@bucket, "db", {:r => 2}).and_return(nil)
105
124
  @bucket.get("db", :r => 2)
106
125
  end
126
+
127
+ it "should disallow fetching an object with a zero-length key" do
128
+ ## TODO: This actually tests the Client object, but there is no suite
129
+ ## of tests for its generic interface.
130
+ expect { @bucket.get('') }.to raise_error(ArgumentError)
131
+ end
107
132
  end
108
133
 
109
134
  describe "creating a new blank object" do
@@ -141,10 +166,35 @@ describe Riak::Bucket do
141
166
  end
142
167
  end
143
168
 
169
+ describe "fetching multiple objects" do
170
+ it 'should get each object individually' do
171
+ @object1 = mock('obj1')
172
+ @object2 = mock('obj2')
173
+ @bucket.should_receive(:[]).with('key1').and_return(@object1)
174
+ @bucket.should_receive(:[]).with('key2').and_return(@object2)
175
+
176
+ @results = @bucket.get_many %w{key1 key2}
177
+
178
+ @results['key1'].should == @object1
179
+ @results['key2'].should == @object2
180
+ end
181
+ end
182
+
144
183
  describe "querying an index" do
145
184
  it "should list the matching keys" do
146
- @backend.should_receive(:get_index).with(@bucket, "test_bin", "testing").and_return(["bar"])
147
- @bucket.get_index("test_bin", "testing").should == ["bar"]
185
+ @backend.
186
+ should_receive(:get_index).
187
+ with(@bucket, "test_bin", "testing", {return_terms: true}).
188
+ and_return(Riak::IndexCollection.new_from_json({
189
+ 'results' => [
190
+ {'testing' => 'asdf'},
191
+ {'testing' => 'hjkl'}]
192
+ }.to_json))
193
+ result = @bucket.get_index("test_bin", "testing", return_terms: true)
194
+
195
+ result.should be_a Riak::IndexCollection
196
+ result.to_a.should == %w{asdf hjkl}
197
+ result.with_terms.should == {'testing' => %w{asdf hjkl}}
148
198
  end
149
199
  end
150
200
 
@@ -196,6 +196,29 @@ describe Riak::Client do
196
196
  end
197
197
  end
198
198
 
199
+ describe "retrieving many values" do
200
+ before :each do
201
+ @client = Riak::Client.new
202
+ @bucket = @client.bucket('foo')
203
+ @bucket.should_receive(:[]).with('value1').and_return(mock('robject'))
204
+ @bucket.should_receive(:[]).with('value2').and_return(mock('robject'))
205
+ @pairs = [
206
+ [@bucket, 'value1'],
207
+ [@bucket, 'value2']
208
+ ]
209
+ end
210
+
211
+ it 'should accept an array of bucket and key pairs' do
212
+ lambda{ @client.get_many(@pairs) }.should_not raise_error
213
+ end
214
+
215
+ it 'should return a hash of bucket/key pairs and robjects' do
216
+ @results = @client.get_many(@pairs)
217
+ @results.should be_a Hash
218
+ @results.length.should be(@pairs.length)
219
+ end
220
+ end
221
+
199
222
  describe "retrieving a bucket" do
200
223
  before :each do
201
224
  @client = Riak::Client.new
@@ -218,6 +241,10 @@ describe Riak::Client do
218
241
  @client.bucket("baz").should == @bucket
219
242
  @client.bucket("baz").should == @bucket
220
243
  end
244
+
245
+ it "should reject buckets with zero-length names" do
246
+ expect { @client.bucket('') }.to raise_error(ArgumentError)
247
+ end
221
248
  end
222
249
 
223
250
  describe "listing buckets" do
@@ -244,6 +271,13 @@ describe Riak::Client do
244
271
  @client.should_receive(:warn)
245
272
  @client.buckets
246
273
  end
274
+
275
+ it "should support a timeout option" do
276
+ @backend.should_receive(:list_buckets).with(timeout: 1234).and_return(%w{test test2})
277
+
278
+ buckets = @client.buckets timeout: 1234
279
+ buckets.should have(2).items
280
+ end
247
281
  end
248
282
 
249
283
  describe "Luwak (large-files) support" do
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::Counter do
4
+ describe "initialization" do
5
+ before :each do
6
+ @bucket = Riak::Bucket.allocate
7
+ @key = 'key'
8
+ @bucket.stub allow_mult: true
9
+ @bucket.stub(client: mock('client'))
10
+ @bucket.stub('is_a?' => true)
11
+ end
12
+
13
+ it "should set the bucket and key" do
14
+ ctr = Riak::Counter.new @bucket, @key
15
+ ctr.bucket.should == @bucket
16
+ ctr.key.should == @key
17
+ end
18
+
19
+ it "should require allow_mult" do
20
+ @bad_bucket = Riak::Bucket.allocate
21
+ @bad_bucket.stub allow_mult: false
22
+ @bad_bucket.stub(client: mock('client'))
23
+
24
+ expect{ctr = Riak::Counter.new @bad_bucket, @key}.to raise_error(ArgumentError)
25
+
26
+ end
27
+ end
28
+
29
+ describe "incrementing and decrementing" do
30
+ before :each do
31
+ @backend = mock 'backend'
32
+
33
+ @client = mock 'client'
34
+ @client.stub(:backend).and_yield @backend
35
+
36
+ @bucket = Riak::Bucket.allocate
37
+ @bucket.stub allow_mult: true
38
+ @bucket.stub client: @client
39
+
40
+ @key = 'key'
41
+
42
+ @ctr = Riak::Counter.new @bucket, @key
43
+
44
+ @increment_expectation = proc{|n| @backend.should_receive(:post_counter).with(@bucket, @key, n, {})}
45
+ end
46
+
47
+ it "should increment by 1 by default" do
48
+ @increment_expectation[1]
49
+ @ctr.increment
50
+ end
51
+
52
+ it "should support incrementing by positive numbers" do
53
+ @increment_expectation[15]
54
+ @ctr.increment 15
55
+ end
56
+
57
+ it "should support incrementing by negative numbers" do
58
+ @increment_expectation[-12]
59
+ @ctr.increment -12
60
+ end
61
+
62
+ it "should decrement by 1 by default" do
63
+ @increment_expectation[-1]
64
+ @ctr.decrement
65
+ end
66
+
67
+ it "should support decrementing by positive numbers" do
68
+ @increment_expectation[-30]
69
+ @ctr.decrement 30
70
+ end
71
+
72
+ it "should support decrementing by negative numbers" do
73
+ @increment_expectation[41]
74
+ @ctr.decrement -41
75
+ end
76
+
77
+ it "should forbid incrementing by non-integers" do
78
+ [1.1, nil, :'1', '1', 2.0/2, [1]].each do |candidate|
79
+ expect do
80
+ @ctr.increment candidate
81
+ raise candidate.to_s
82
+ end.to raise_error(ArgumentError)
83
+ end
84
+ end
85
+ end
86
+
87
+ describe "failure modes" do
88
+ before :each do
89
+ @nodes = 10.times.map do |n|
90
+ {pb_port: "100#{n}7"}
91
+ end
92
+
93
+ @fake_pool = mock 'pool'
94
+ @backend = mock 'backend'
95
+
96
+ @client = Riak::Client.new nodes: @nodes, protocol: 'pbc'
97
+ @client.instance_variable_set :@protobuffs_pool, @fake_pool
98
+
99
+ @fake_pool.stub(:take).and_yield(@backend)
100
+
101
+ @bucket = Riak::Bucket.allocate
102
+ @bucket.stub allow_mult: true
103
+ @bucket.stub client: @client
104
+
105
+ @key = 'key'
106
+
107
+ @expect_post = @backend.should_receive(:post_counter).with(@bucket, @key, 1, {})
108
+
109
+ @ctr = Riak::Counter.new @bucket, @key
110
+ end
111
+
112
+ it "should not retry on timeout" do
113
+ @expect_post.once.and_raise('timeout')
114
+ expect(proc { @ctr.increment }).to raise_error
115
+ end
116
+
117
+ it "should not retry on quorum failure" do
118
+ @expect_post.once.and_raise('quorum not satisfied')
119
+ expect(proc { @ctr.increment }).to raise_error
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::IndexCollection do
4
+ describe "json initialization" do
5
+ it "should accept a list of keys" do
6
+ @input = {
7
+ 'keys' => %w{first second third}
8
+ }.to_json
9
+ lambda { @coll = Riak::IndexCollection.new_from_json @input }.should_not raise_error
10
+ %w{first second third}.should == @coll
11
+ end
12
+ it "should accept a list of keys and a continuation" do
13
+ @input = {
14
+ 'keys' => %w{first second third},
15
+ 'continuation' => 'examplecontinuation'
16
+ }.to_json
17
+ lambda { @coll = Riak::IndexCollection.new_from_json @input }.should_not raise_error
18
+ %w{first second third}.should == @coll
19
+ @coll.continuation.should == 'examplecontinuation'
20
+ end
21
+ it "should accept a list of results hashes" do
22
+ @input = {
23
+ 'results' => [
24
+ {'first' => 'first'},
25
+ {'second' => 'second'},
26
+ {'second' => 'other'}
27
+ ]
28
+ }.to_json
29
+
30
+ lambda { @coll = Riak::IndexCollection.new_from_json @input }.should_not raise_error
31
+ %w{first second other}.should == @coll
32
+ {'first' => %w{first}, 'second' => %w{second other}}.should == @coll.with_terms
33
+ end
34
+ it "should accept a list of results hashes and a continuation" do
35
+ @input = {
36
+ 'results' => [
37
+ {'first' => 'first'},
38
+ {'second' => 'second'},
39
+ {'second' => 'other'}
40
+ ],
41
+ 'continuation' => 'examplecontinuation'
42
+ }.to_json
43
+
44
+ lambda { @coll = Riak::IndexCollection.new_from_json @input }.should_not raise_error
45
+ %w{first second other}.should == @coll
46
+ @coll.continuation.should == 'examplecontinuation'
47
+ {'first' => %w{first}, 'second' => %w{second other}}.should == @coll.with_terms
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Riak::ListBuckets do
4
+ before :each do
5
+ @client = Riak::Client.new protocol: 'pbc'
6
+ @backend = mock 'backend'
7
+ @fake_pool = mock 'connection pool'
8
+ @fake_pool.stub(:take).and_yield(@backend)
9
+
10
+ @expect_list = @backend.should_receive(:list_buckets)
11
+
12
+ @client.instance_variable_set :@protobuffs_pool, @fake_pool
13
+ end
14
+
15
+ describe "non-streaming" do
16
+ it 'should call the backend without a block' do
17
+ @expect_list.with({}).and_return(%w{a b c d})
18
+
19
+ @client.list_buckets
20
+ end
21
+ end
22
+
23
+ describe "streaming" do
24
+ it 'should call the backend with a block' do
25
+ @expect_list.
26
+ and_yield(%w{abc abd abe}).
27
+ and_yield(%w{bbb ccc ddd})
28
+
29
+ @yielded = []
30
+
31
+ @client.list_buckets do |bucket|
32
+ @yielded << bucket
33
+ end
34
+
35
+ @yielded.each do |b|
36
+ b.should be_a Riak::Bucket
37
+ end
38
+ @yielded.map(&:name).should == %w{abc abd abe bbb ccc ddd}
39
+ end
40
+ end
41
+ end