riak-client 1.0.0.beta → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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