riak-client 1.0.0.beta → 1.0.0

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 (69) hide show
  1. data/.gitignore +7 -4
  2. data/Gemfile +12 -17
  3. data/Guardfile +1 -1
  4. data/LICENSE +16 -0
  5. data/README.markdown +178 -0
  6. data/RELEASE_NOTES.md +99 -0
  7. data/Rakefile +25 -1
  8. data/erl_src/riak_kv_test014_backend.beam +0 -0
  9. data/erl_src/riak_kv_test014_backend.erl +189 -0
  10. data/erl_src/riak_kv_test_backend.beam +0 -0
  11. data/erl_src/riak_kv_test_backend.erl +37 -19
  12. data/lib/riak/client.rb +322 -272
  13. data/lib/riak/client/beefcake_protobuffs_backend.rb +6 -10
  14. data/lib/riak/client/decaying.rb +28 -0
  15. data/lib/riak/client/excon_backend.rb +27 -11
  16. data/lib/riak/client/http_backend.rb +71 -2
  17. data/lib/riak/client/http_backend/configuration.rb +17 -3
  18. data/lib/riak/client/http_backend/transport_methods.rb +3 -3
  19. data/lib/riak/client/net_http_backend.rb +18 -14
  20. data/lib/riak/client/node.rb +111 -0
  21. data/lib/riak/client/pool.rb +180 -0
  22. data/lib/riak/client/protobuffs_backend.rb +15 -5
  23. data/lib/riak/client/search.rb +9 -3
  24. data/lib/riak/link.rb +5 -7
  25. data/lib/riak/locale/en.yml +1 -0
  26. data/lib/riak/node/configuration.rb +1 -0
  27. data/lib/riak/node/defaults.rb +19 -6
  28. data/lib/riak/node/generation.rb +9 -2
  29. data/lib/riak/node/log.rb +2 -2
  30. data/lib/riak/node/version.rb +22 -16
  31. data/lib/riak/robject.rb +19 -3
  32. data/lib/riak/serializers.rb +1 -1
  33. data/lib/riak/test_server.rb +10 -2
  34. data/lib/riak/version.rb +1 -1
  35. data/riak-client.gemspec +3 -3
  36. data/spec/failover/failover.rb +59 -0
  37. data/spec/integration/riak/http_backends_spec.rb +2 -2
  38. data/spec/integration/riak/node_spec.rb +16 -24
  39. data/spec/integration/riak/protobuffs_backends_spec.rb +1 -1
  40. data/spec/integration/riak/test_server_spec.rb +4 -3
  41. data/spec/integration/riak/threading_spec.rb +193 -0
  42. data/spec/riak/beefcake_protobuffs_backend/object_methods_spec.rb +23 -0
  43. data/spec/riak/beefcake_protobuffs_backend_spec.rb +4 -2
  44. data/spec/riak/bucket_spec.rb +2 -1
  45. data/spec/riak/client_spec.rb +80 -181
  46. data/spec/riak/excon_backend_spec.rb +3 -2
  47. data/spec/riak/http_backend/configuration_spec.rb +37 -5
  48. data/spec/riak/http_backend/object_methods_spec.rb +1 -1
  49. data/spec/riak/http_backend/transport_methods_spec.rb +2 -2
  50. data/spec/riak/http_backend_spec.rb +53 -3
  51. data/spec/riak/map_reduce_spec.rb +1 -1
  52. data/spec/riak/net_http_backend_spec.rb +1 -2
  53. data/spec/riak/node_spec.rb +173 -0
  54. data/spec/riak/pool_spec.rb +306 -0
  55. data/spec/riak/robject_spec.rb +8 -4
  56. data/spec/riak/search_spec.rb +66 -15
  57. data/spec/riak/serializers_spec.rb +12 -1
  58. data/spec/spec_helper.rb +9 -1
  59. data/spec/support/http_backend_implementation_examples.rb +6 -2
  60. data/spec/support/sometimes.rb +46 -0
  61. data/spec/support/test_server.rb +50 -19
  62. data/spec/support/unified_backend_examples.rb +11 -10
  63. data/spec/support/version_filter.rb +14 -0
  64. metadata +40 -29
  65. data/lib/active_support/cache/riak_store.rb +0 -2
  66. data/lib/riak/cache_store.rb +0 -84
  67. data/lib/riak/client/pump.rb +0 -30
  68. data/lib/riak/util/fiber1.8.rb +0 -48
  69. data/spec/integration/riak/cache_store_spec.rb +0 -129
data/lib/riak/robject.rb CHANGED
@@ -50,14 +50,29 @@ module Riak
50
50
  # @see http://wiki.basho.com/display/RIAK/REST+API#RESTAPI-Storeaneworexistingobjectwithakey Riak Rest API Docs
51
51
  attr_accessor :prevent_stale_writes
52
52
 
53
+ # Defines a callback to be invoked when there is conflict.
54
+ #
55
+ # @yield The conflict callback.
56
+ # @yieldparam [RObject] robject The conflicted RObject
57
+ # @yieldreturn [RObject, nil] Either the resolved RObject or nil if your
58
+ # callback cannot resolve it. The next registered
59
+ # callback will be given the chance to resolve it.
60
+ #
61
+ # @note Ripple registers its own document-level conflict handler, so if you're
62
+ # using ripple, you will probably want to use that instead.
53
63
  def self.on_conflict(&conflict_hook)
54
64
  on_conflict_hooks << conflict_hook
55
65
  end
56
66
 
67
+ # @return [Array<Proc>] the list of registered conflict callbacks.
57
68
  def self.on_conflict_hooks
58
69
  @on_conflict_hooks ||= []
59
70
  end
60
71
 
72
+ # Attempts to resolve conflict using the registered conflict callbacks.
73
+ #
74
+ # @return [RObject] the RObject
75
+ # @note There is no guarantee the returned RObject will have been resolved
61
76
  def attempt_conflict_resolution
62
77
  return self unless conflict?
63
78
 
@@ -195,9 +210,10 @@ module Riak
195
210
 
196
211
  # Returns sibling objects when in conflict.
197
212
  # @return [Array<RObject>] an array of conflicting sibling objects for this key
198
- # @return [self] this object when not in conflict
213
+ # @return [Array<self>] a single-element array containing object when not
214
+ # in conflict
199
215
  def siblings
200
- return self unless conflict?
216
+ return [self] unless conflict?
201
217
  @siblings
202
218
  end
203
219
 
@@ -244,7 +260,7 @@ module Riak
244
260
  # @param [Array<Hash,WalkSpec>] link specifications for the query
245
261
  def walk(*params)
246
262
  specs = WalkSpec.normalize(*params)
247
- @bucket.client.http.link_walk(self, specs)
263
+ @bucket.client.link_walk(self, specs)
248
264
  end
249
265
 
250
266
  # Converts the object to a link suitable for linking other objects
@@ -22,7 +22,7 @@ module Riak
22
22
  private
23
23
 
24
24
  def serializer_for(content_type)
25
- serializers.fetch(content_type) do
25
+ serializers.fetch(content_type[/^[^;\s]+/]) do
26
26
  raise NotImplementedError.new(t('serializer_not_implemented', :content_type => content_type.inspect))
27
27
  end
28
28
  end
@@ -17,7 +17,6 @@ module Riak
17
17
  configuration[:env] ||= {}
18
18
  configuration[:env][:riak_kv] ||= {}
19
19
  (configuration[:env][:riak_kv][:add_paths] ||= []) << File.expand_path("../../../erl_src", __FILE__)
20
- configuration[:env][:riak_kv][:storage_backend] = :riak_kv_test_backend
21
20
  configuration[:env][:riak_search] ||= {}
22
21
  configuration[:env][:riak_search][:search_backend] = :riak_search_test_backend
23
22
  super configuration
@@ -52,7 +51,7 @@ module Riak
52
51
  def drop
53
52
  begin
54
53
  maybe_attach
55
- @console.command "riak_kv_test_backend:reset()."
54
+ @console.command "#{kv_backend}:reset()."
56
55
  @console.command "riak_search_test_backend:reset()."
57
56
  rescue IOError
58
57
  retry
@@ -71,5 +70,14 @@ module Riak
71
70
  def open?
72
71
  @console && @console.open?
73
72
  end
73
+
74
+ def configure_data
75
+ super
76
+ if version < "1.0.0"
77
+ env[:riak_kv][:storage_backend] = :riak_kv_test014_backend
78
+ else
79
+ env[:riak_kv][:storage_backend] = :riak_kv_test_backend
80
+ end
81
+ end
74
82
  end
75
83
  end
data/lib/riak/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Riak
2
- VERSION = "1.0.0.beta"
2
+ VERSION = "1.0.0"
3
3
  end
data/riak-client.gemspec CHANGED
@@ -8,15 +8,15 @@ Gem::Specification.new do |gem|
8
8
  gem.summary = %Q{riak-client is a rich client for Riak, the distributed database by Basho.}
9
9
  gem.description = %Q{riak-client is a rich client for Riak, the distributed database by Basho. It supports the full HTTP and Protocol Buffers interfaces including storage operations, bucket configuration, link-walking, secondary indexes and map-reduce.}
10
10
  gem.email = ["sean@basho.com"]
11
- gem.homepage = "http://seancribbs.github.com/ripple"
11
+ gem.homepage = "http://github.com/basho/riak-ruby-client"
12
12
  gem.authors = ["Sean Cribbs"]
13
13
 
14
14
  # Deps
15
- gem.add_development_dependency "rspec", "~>2.6.0"
15
+ gem.add_development_dependency "rspec", "~>2.8.0"
16
16
  gem.add_development_dependency "fakeweb", ">=1.2"
17
17
  gem.add_development_dependency "rack", ">=1.0"
18
18
  gem.add_development_dependency "excon", "~>0.6.1"
19
- gem.add_development_dependency 'rake', '~> 0.8.7'
19
+ gem.add_development_dependency 'rake'
20
20
  gem.add_runtime_dependency "i18n", ">=0.4.0"
21
21
  gem.add_runtime_dependency "builder", ">= 2.1.2"
22
22
  gem.add_runtime_dependency "beefcake", "~>0.3.7"
@@ -0,0 +1,59 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
+ require 'riak'
3
+
4
+ # This is not a formal spec yet. It's designed to be run agains a local dev
5
+ # cluster while you bring nodes up and down.
6
+ [
7
+ {:protocol => 'pbc', :protobuffs_backend => :Beefcake},
8
+ {:protocol => 'http', :http_backend => :NetHTTP},
9
+ {:protocol => 'http', :http_backend => :Excon}
10
+ ].each do |opts|
11
+ @client = Riak::Client.new(
12
+ {
13
+ :nodes => (1..3).map { |i|
14
+ {
15
+ :http_port => 8090 + i,
16
+ :pb_port => 8080 + i
17
+ }
18
+ }
19
+ }.merge(opts)
20
+ )
21
+
22
+ errors = []
23
+ p opts
24
+
25
+ n = 10
26
+ c = 1000
27
+
28
+ (0...n).map do |t|
29
+ Thread.new do
30
+ # Generate a stream of put reqs. Put a . for each success, an X for
31
+ # each failure.
32
+ c.times do |i|
33
+ begin
34
+ o = @client['test'].new("#{t}:#{i}")
35
+ o.content_type = 'text/plain'
36
+ o.data = i.to_s
37
+ o.store
38
+ o2 = @client['test'].get("#{t}:#{i}")
39
+ o2.data == i.to_s or raise "wrong data"
40
+ print '.'
41
+ rescue => e
42
+ print 'X'
43
+ errors << e
44
+ end
45
+ end
46
+ end
47
+ end.each do |thread|
48
+ thread.join
49
+ end
50
+
51
+ # Put errors
52
+ puts
53
+ errors.each do |e|
54
+ puts e.inspect
55
+ puts e.backtrace.map { |x| " #{x}" }.join("\n")
56
+ end
57
+
58
+ puts "\n\n"
59
+ end
@@ -11,7 +11,7 @@ describe "HTTP" do
11
11
  if bklass.configured?
12
12
  describe klass.to_s do
13
13
  before do
14
- @backend = bklass.new(@client)
14
+ @backend = bklass.new(@client, @client.node)
15
15
  end
16
16
 
17
17
  it_should_behave_like "Unified backend API"
@@ -34,7 +34,7 @@ describe "HTTP" do
34
34
  end
35
35
 
36
36
  describe 'NetHTTPBackend' do
37
- subject { Riak::Client::NetHTTPBackend.new(@client) }
37
+ subject { Riak::Client::NetHTTPBackend.new(@client, @client.node) }
38
38
  let(:file) { File.open(__FILE__) }
39
39
  let(:sized) { Reader.new(["foo", "bar", "baz"]) }
40
40
  let(:sizeless) { SizelessReader.new(["foo", "bar", "baz"]) }
@@ -4,34 +4,26 @@ require 'yaml'
4
4
 
5
5
  describe Riak::Node, :test_server => false, :slow => true do
6
6
  let(:test_server_config){ YAML.load_file("spec/support/test_server.yml") }
7
- subject { described_class.new(:root => ".ripplenode", :source => test_server_config['source']) }
8
- after { subject.stop if subject.started? }
9
- after(:all) { subject.destroy }
7
+ let(:node){ described_class.new(:root => ".ripplenode", :source => test_server_config['source']) }
8
+ subject { node }
9
+ after { node.stop if node.started? }
10
+ after(:all) { node.destroy }
11
+
12
+ context "finding the base_dir and version" do
13
+ its(:base_dir) { should be_directory }
14
+ its(:version) { should match /^\d+.\d+.\d+$/ }
15
+
16
+ context "when the base directory is missing" do
17
+ before { Pathname.any_instance.stub(:each_line).and_return([]) }
18
+ its(:base_dir) { should be_nil }
19
+ its(:version) { should be_nil }
20
+ end
21
+ end
10
22
 
11
23
  context "creation" do
12
24
  before { subject.create }
13
25
  after { subject.destroy }
14
26
 
15
- describe "finding the base_dir and version" do
16
- it "should return a valid directory for base_dir" do
17
- subject.base_dir.should be_exist
18
- end
19
-
20
- it "should read a version from the releases directory" do
21
- subject.version.should match /\d+.\d+.\d+/
22
- end
23
-
24
- it "should return nil for base_dir if RUNNER_BASE_DIR is not found" do
25
- Pathname.any_instance.stub(:readlines).and_return([])
26
- subject.base_dir.should be_nil
27
- end
28
-
29
- it "should return nil for version if base_dir is nil" do
30
- Pathname.any_instance.stub(:readlines).and_return([])
31
- subject.version.should be_nil
32
- end
33
- end
34
-
35
27
  describe "generating the manifest" do
36
28
  it "should store the configuration manifest in the node directory" do
37
29
  (subject.root + '.node.yml').should be_exist
@@ -175,7 +167,7 @@ describe Riak::Node, :test_server => false, :slow => true do
175
167
  it "should read the console log" do
176
168
  if subject.version >= "1.0.0"
177
169
  subject.read_console_log(:debug, :info, :notice).should_not be_empty
178
- subject.read_console_log(:debug..:emergency).should_not be_empty
170
+ subject.read_console_log('debug'..'emergency').should_not be_empty
179
171
  subject.read_console_log(:info).should_not be_empty
180
172
  subject.read_console_log(:foo).should be_empty
181
173
  end
@@ -11,7 +11,7 @@ describe "Protocol Buffers" do
11
11
  if bklass.configured?
12
12
  describe klass.to_s do
13
13
  before do
14
- @backend = bklass.new(@client)
14
+ @backend = bklass.new(@client, @client.node)
15
15
  end
16
16
 
17
17
  it_should_behave_like "Unified backend API"
@@ -12,9 +12,10 @@ describe Riak::TestServer do
12
12
  end
13
13
 
14
14
  it "should use the KV test backend" do
15
- subject.kv_backend.should == :riak_kv_test_backend
16
- subject.env[:riak_kv][:storage_backend].should == :riak_kv_test_backend
17
- app_config.should include("{storage_backend, riak_kv_test_backend}")
15
+ backend = subject.version < "1.0.0" ? :riak_kv_test014_backend : :riak_kv_test_backend
16
+ subject.kv_backend.should == backend
17
+ subject.env[:riak_kv][:storage_backend].should == backend
18
+ app_config.should include("{storage_backend, #{backend}}")
18
19
  end
19
20
 
20
21
  it "should use the Search test backend" do
@@ -0,0 +1,193 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Multithreaded client", :test_server => true do
4
+ class Synchronizer
5
+ def initialize(n)
6
+ @mutex = Mutex.new
7
+ @n = n
8
+ @waiting = Set.new
9
+ end
10
+
11
+ def sync
12
+ stop = false
13
+ @mutex.synchronize do
14
+ @waiting << Thread.current
15
+
16
+ if @waiting.size >= @n
17
+ # All threads are waiting.
18
+ @waiting.each do |t|
19
+ t.run
20
+ end
21
+ else
22
+ stop = true
23
+ end
24
+ end
25
+
26
+ if stop
27
+ Thread.stop
28
+ end
29
+ end
30
+ end
31
+
32
+ def threads(n, opts = {})
33
+ if opts[:synchronize]
34
+ s1 = Synchronizer.new n
35
+ s2 = Synchronizer.new n
36
+ end
37
+
38
+ threads = (0...n).map do |i|
39
+ Thread.new do
40
+ if opts[:synchronize]
41
+ s1.sync
42
+ end
43
+
44
+ yield i
45
+
46
+ if opts[:synchronize]
47
+ s2.sync
48
+ end
49
+ end
50
+ end
51
+
52
+ threads.each do |t|
53
+ t.join
54
+ end
55
+ end
56
+
57
+ [
58
+ {:protocol => 'pbc', :protobuffs_backend => :Beefcake},
59
+ {:protocol => 'http', :http_backend => :NetHTTP},
60
+ {:protocol => 'http', :http_backend => :Excon}
61
+ ].each do |opts|
62
+ describe opts.inspect do
63
+ before do
64
+ @pb_port ||= $test_server.pb_port
65
+ @http_port ||= $test_server.http_port
66
+ @client = Riak::Client.new({
67
+ :pb_port => @pb_port,
68
+ :http_port => @http_port
69
+ }.merge(opts))
70
+ end
71
+
72
+ it 'should get in parallel' do
73
+ data = "the gun is good"
74
+ ro = @client['test'].new('test')
75
+ ro.content_type = "application/json"
76
+ ro.data = [data]
77
+ ro.store
78
+
79
+ threads 10, :synchronize => true do
80
+ x = @client['test']['test']
81
+ x.content_type.should == "application/json"
82
+ x.data.should == [data]
83
+ end
84
+ end
85
+
86
+ it 'should put in parallel' do
87
+ data = "the tabernacle is indestructible and everlasting"
88
+
89
+ n = 10
90
+ threads n, :synchronize => true do |i|
91
+ x = @client['test'].new("test-#{i}")
92
+ x.content_type = "application/json"
93
+ x.data = ["#{data}-#{i}"]
94
+ x.store
95
+ end
96
+
97
+ (0...n).each do |i|
98
+ read = @client['test']["test-#{i}"]
99
+ read.content_type.should == "application/json"
100
+ read.data.should == ["#{data}-#{i}"]
101
+ end
102
+ end
103
+
104
+ # This is a 1.0+ spec because putting with the same client ID
105
+ # will not create siblings on 0.14 in the same way. This will
106
+ # also likely fail for nodes with vnode_vclocks = false.
107
+ it 'should put conflicts in parallel', :version => "1.0.0" do
108
+ @client['test'].allow_mult = true
109
+ @client['test'].allow_mult.should == true
110
+
111
+ init = @client['test'].new('test')
112
+ init.content_type = "application/json"
113
+ init.data = ''
114
+ init.store
115
+
116
+ # Create conflicting writes
117
+ n = 10
118
+ s = Synchronizer.new n
119
+ threads n, :synchronize => true do |i|
120
+ x = @client['test']["test"]
121
+ s.sync
122
+ x.data = [i]
123
+ x.store
124
+ end
125
+
126
+ read = @client['test']["test"]
127
+ read.conflict?.should == true
128
+ read.siblings.map do |sibling|
129
+ sibling.data.first
130
+ end.to_set.should == (0...n).to_set
131
+ end
132
+
133
+ it 'should list-keys and get in parallel', :slow => true do
134
+ count = 100
135
+ threads = 2
136
+
137
+ # Create items
138
+ count.times do |i|
139
+ o = @client['test'].new("#{i}")
140
+ o.content_type = 'application/json'
141
+ o.data = [i]
142
+ o.store
143
+ end
144
+
145
+ threads(threads) do
146
+ set = Set.new
147
+ @client['test'].keys do |stream|
148
+ stream.each do |key|
149
+ set.merge @client['test'][key].data
150
+ end
151
+ end
152
+ set.should == (0...count).to_set
153
+ end
154
+ end
155
+
156
+ sometimes 'should mapreduce in parallel' do
157
+ if ("1.0.0"..."1.1.0").include?(test_server.version)
158
+ # On a fresh node, this module might not have been loaded yet
159
+ # and the mapred test exposes a race condition in riak_pipe_v
160
+ # when verifying function validity. This race condition is
161
+ # fixed in 1.1.
162
+ test_server.with_console do |console|
163
+ console.command 'code:load(riak_kv_pipe_get), ok.'
164
+ console.command 'code:load(riak_kv_mrc_map), ok.'
165
+ end
166
+ end
167
+
168
+ count = 10
169
+ threads = 10
170
+
171
+ # Create items
172
+ count.times do |i|
173
+ o = @client['test'].new("#{i}")
174
+ o.content_type = 'application/json'
175
+ o.data = i
176
+ o.store
177
+ end
178
+
179
+ # Ze mapreduce
180
+ threads(threads) do
181
+ # Mapreduce
182
+ (0...count).inject(Riak::MapReduce.new(@client)) do |mr, i|
183
+ mr.add('test', i.to_s)
184
+ end.map(%{function(v) {
185
+ return [v.key];
186
+ }}, :keep => true).run.map do |s|
187
+ s.to_i
188
+ end.to_set.should == (0...count).to_set
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end