riak-client 0.7.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 (43) hide show
  1. data/Rakefile +74 -0
  2. data/lib/riak.rb +49 -0
  3. data/lib/riak/bucket.rb +176 -0
  4. data/lib/riak/cache_store.rb +82 -0
  5. data/lib/riak/client.rb +139 -0
  6. data/lib/riak/client/curb_backend.rb +82 -0
  7. data/lib/riak/client/http_backend.rb +209 -0
  8. data/lib/riak/client/net_http_backend.rb +49 -0
  9. data/lib/riak/failed_request.rb +37 -0
  10. data/lib/riak/i18n.rb +20 -0
  11. data/lib/riak/invalid_response.rb +25 -0
  12. data/lib/riak/link.rb +73 -0
  13. data/lib/riak/locale/en.yml +37 -0
  14. data/lib/riak/map_reduce.rb +248 -0
  15. data/lib/riak/map_reduce_error.rb +20 -0
  16. data/lib/riak/robject.rb +267 -0
  17. data/lib/riak/util/escape.rb +12 -0
  18. data/lib/riak/util/fiber1.8.rb +48 -0
  19. data/lib/riak/util/headers.rb +44 -0
  20. data/lib/riak/util/multipart.rb +52 -0
  21. data/lib/riak/util/translation.rb +29 -0
  22. data/lib/riak/walk_spec.rb +117 -0
  23. data/spec/fixtures/cat.jpg +0 -0
  24. data/spec/fixtures/multipart-blank.txt +7 -0
  25. data/spec/fixtures/multipart-with-body.txt +16 -0
  26. data/spec/integration/riak/cache_store_spec.rb +129 -0
  27. data/spec/riak/bucket_spec.rb +247 -0
  28. data/spec/riak/client_spec.rb +174 -0
  29. data/spec/riak/curb_backend_spec.rb +53 -0
  30. data/spec/riak/escape_spec.rb +21 -0
  31. data/spec/riak/headers_spec.rb +34 -0
  32. data/spec/riak/http_backend_spec.rb +131 -0
  33. data/spec/riak/link_spec.rb +82 -0
  34. data/spec/riak/map_reduce_spec.rb +352 -0
  35. data/spec/riak/multipart_spec.rb +36 -0
  36. data/spec/riak/net_http_backend_spec.rb +28 -0
  37. data/spec/riak/object_spec.rb +538 -0
  38. data/spec/riak/walk_spec_spec.rb +208 -0
  39. data/spec/spec_helper.rb +30 -0
  40. data/spec/support/http_backend_implementation_examples.rb +215 -0
  41. data/spec/support/mock_server.rb +61 -0
  42. data/spec/support/mocks.rb +3 -0
  43. metadata +187 -0
@@ -0,0 +1,247 @@
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::Bucket do
17
+ before :each do
18
+ @client = Riak::Client.new
19
+ @bucket = Riak::Bucket.new(@client, "foo")
20
+ end
21
+
22
+ def do_load(overrides={})
23
+ @bucket.load({
24
+ :body => '{"props":{"name":"foo","allow_mult":false,"big_vclock":50,"chash_keyfun":{"mod":"riak_util","fun":"chash_std_keyfun"},"linkfun":{"mod":"jiak_object","fun":"mapreduce_linkfun"},"n_val":3,"old_vclock":86400,"small_vclock":10,"young_vclock":20},"keys":["bar"]}',
25
+ :headers => {
26
+ "vary" => ["Accept-Encoding"],
27
+ "server" => ["MochiWeb/1.1 WebMachine/1.5.1 (hack the charles gibson)"],
28
+ "link" => ['</riak/foo/bar>; riaktag="contained"'],
29
+ "date" => ["Tue, 12 Jan 2010 15:30:43 GMT"],
30
+ "content-type" => ["application/json"],
31
+ "content-length" => ["257"]
32
+ }
33
+ }.merge(overrides))
34
+ end
35
+
36
+
37
+ describe "when initializing" do
38
+ it "should require a client and a name" do
39
+ lambda { Riak::Bucket.new }.should raise_error
40
+ lambda { Riak::Bucket.new(@client) }.should raise_error
41
+ lambda { Riak::Bucket.new("foo") }.should raise_error
42
+ lambda { Riak::Bucket.new("foo", @client) }.should raise_error
43
+ lambda { Riak::Bucket.new(@client, "foo") }.should_not raise_error
44
+ end
45
+
46
+ it "should set the client and name attributes" do
47
+ bucket = Riak::Bucket.new(@client, "foo")
48
+ bucket.client.should == @client
49
+ bucket.name.should == "foo"
50
+ end
51
+ end
52
+
53
+ describe "when loading data from an HTTP response" do
54
+ it "should load the bucket properties from the response body" do
55
+ do_load
56
+ @bucket.props.should == {"name"=>"foo","allow_mult" => false,"big_vclock" => 50,"chash_keyfun" => {"mod" =>"riak_util","fun"=>"chash_std_keyfun"},"linkfun"=>{"mod"=>"jiak_object","fun"=>"mapreduce_linkfun"},"n_val"=>3,"old_vclock"=>86400,"small_vclock"=>10,"young_vclock"=>20}
57
+ end
58
+
59
+ it "should load the keys from the response body" do
60
+ do_load
61
+ @bucket.keys.should == ["bar"]
62
+ end
63
+
64
+ it "should raise an error for a response that is not JSON" do
65
+ lambda do
66
+ do_load(:headers => {"content-type" => ["text/plain"]})
67
+ end.should raise_error(Riak::InvalidResponse)
68
+ end
69
+
70
+ it "should unescape key names" do
71
+ do_load(:body => '{"keys":["foo", "bar%20baz"]}')
72
+ @bucket.keys.should == ["foo", "bar baz"]
73
+ end
74
+ end
75
+
76
+ describe "accessing keys" do
77
+ before :each do
78
+ @http = mock("HTTPBackend")
79
+ @client.stub!(:http).and_return(@http)
80
+ end
81
+
82
+ it "should load the keys if not present" do
83
+ @http.should_receive(:get).with(200, "/riak/", "foo", {:props => false}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar"]}'})
84
+ @bucket.keys.should == ["bar"]
85
+ end
86
+
87
+ it "should allow reloading of the keys" do
88
+ @http.should_receive(:get).with(200, "/riak/","foo", {:props => false}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar"]}'})
89
+ do_load # Ensures they're already loaded
90
+ @bucket.keys(:reload => true).should == ["bar"]
91
+ end
92
+
93
+ it "should allow streaming keys through block" do
94
+ # pending "Needs support in the raw_http_resource"
95
+ @http.should_receive(:get).with(200, "/riak/","foo", {:props => false, :keys => "stream"}, {}).and_yield("{}").and_yield('{"keys":[]}').and_yield('{"keys":["bar"]}').and_yield('{"keys":["baz"]}')
96
+ all_keys = []
97
+ @bucket.keys do |list|
98
+ all_keys.concat(list)
99
+ end
100
+ all_keys.should == ["bar", "baz"]
101
+ end
102
+
103
+ it "should unescape key names" do
104
+ @http.should_receive(:get).with(200, "/riak/","foo", {:props => false}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar%20baz"]}'})
105
+ @bucket.keys.should == ["bar baz"]
106
+ end
107
+
108
+ it "should escape the bucket name" do
109
+ @bucket.instance_variable_set :@name, "unescaped "
110
+ @http.should_receive(:get).with(200, "/riak/","unescaped%20", {:props => false}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"keys":["bar"]}'})
111
+ @bucket.keys.should == ["bar"]
112
+ end
113
+ end
114
+
115
+ describe "setting the bucket properties" do
116
+ before :each do
117
+ @http = mock("HTTPBackend")
118
+ @client.stub!(:http).and_return(@http)
119
+ end
120
+
121
+ it "should PUT the new properties to the bucket" do
122
+ @http.should_receive(:put).with(204, "/riak/","foo", '{"props":{"name":"foo"}}', {"Content-Type" => "application/json"}).and_return({:body => "", :headers => {}})
123
+ @bucket.props = { :name => "foo" }
124
+ end
125
+
126
+ it "should raise an error if an invalid property is given" do
127
+ lambda { @bucket.props = "blah" }.should raise_error(ArgumentError)
128
+ end
129
+
130
+ it "should escape the bucket name" do
131
+ @bucket.instance_variable_set :@name, "foo bar"
132
+ @http.should_receive(:put).with(204, "/riak/","foo%20bar", '{"props":{"n_val":2}}', {"Content-Type" => "application/json"}).and_return({:body => "", :headers => {}})
133
+ @bucket.props = {:n_val => 2}
134
+ end
135
+ end
136
+
137
+ describe "fetching an object" do
138
+ before :each do
139
+ @http = mock("HTTPBackend")
140
+ @client.stub!(:http).and_return(@http)
141
+ end
142
+
143
+ it "should load the object from the server as a Riak::RObject" do
144
+ @http.should_receive(:get).with(200, "/riak/","foo", "db", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
145
+ @bucket.get("db").should be_kind_of(Riak::RObject)
146
+ end
147
+
148
+ it "should use the given query parameters (for R value, etc)" do
149
+ @http.should_receive(:get).with(200, "/riak/","foo", "db", {:r => 2}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
150
+ @bucket.get("db", :r => 2).should be_kind_of(Riak::RObject)
151
+ end
152
+
153
+ it "should allow 300 responses if allow_mult is set" do
154
+ @bucket.instance_variable_set(:@props, {'allow_mult' => true})
155
+ @http.should_receive(:get).with([200,300], "/riak/","foo", "db", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
156
+ @bucket.get('db')
157
+ end
158
+
159
+ it "should escape the bucket and key names" do
160
+ @bucket.instance_variable_set(:@name, "foo ")
161
+ @http.should_receive(:get).with(200, "/riak/","foo%20", "%20bar", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
162
+ @bucket.get(' bar')
163
+ end
164
+ end
165
+
166
+ describe "creating a new blank object" do
167
+ it "should instantiate the object with the given key, default to JSON" do
168
+ obj = @bucket.new('bar')
169
+ obj.should be_kind_of(Riak::RObject)
170
+ obj.key.should == 'bar'
171
+ obj.content_type.should == 'application/json'
172
+ end
173
+ end
174
+
175
+ describe "fetching or creating a new object" do
176
+ before :each do
177
+ @http = mock("HTTPBackend")
178
+ @client.stub!(:http).and_return(@http)
179
+ end
180
+
181
+ it "should return the existing object if present" do
182
+ @http.should_receive(:get).with(200, "/riak/","foo", "db", {}, {}).and_return({:headers => {"content-type" => ["application/json"]}, :body => '{"name":"Riak","company":"Basho"}'})
183
+ obj = @bucket.get_or_new('db')
184
+ obj.key.should == 'db'
185
+ obj.data['name'].should == "Riak"
186
+ end
187
+
188
+ it "should create a new blank object if the key does not exist" do
189
+ @http.should_receive(:get).and_raise(Riak::FailedRequest.new(:get, 200, 404, {}, "File not found"))
190
+ obj = @bucket.get_or_new('db')
191
+ obj.key.should == 'db'
192
+ obj.data.should be_blank
193
+ end
194
+
195
+ it "should bubble up non-ok non-missing errors" do
196
+ @http.should_receive(:get).and_raise(Riak::FailedRequest.new(:get, 200, 500, {}, "File not found"))
197
+ lambda { @bucket.get_or_new('db') }.should raise_error(Riak::FailedRequest)
198
+ end
199
+ end
200
+
201
+ describe "get/set allow_mult property" do
202
+ before :each do
203
+ do_load
204
+ end
205
+
206
+ it "should extract the allow_mult property" do
207
+ @bucket.allow_mult.should be_false
208
+ end
209
+
210
+ it "should set the allow_mult property" do
211
+ @bucket.should_receive(:props=).with(hash_including('allow_mult' => true))
212
+ @bucket.allow_mult = true
213
+ end
214
+ end
215
+
216
+ describe "get/set the N value" do
217
+ before :each do
218
+ do_load
219
+ end
220
+
221
+ it "should extract the N value" do
222
+ @bucket.n_value.should == 3
223
+ end
224
+
225
+ it "should set the N value" do
226
+ @bucket.should_receive(:props=).with(hash_including('n_val' => 1))
227
+ @bucket.n_value = 1
228
+ end
229
+ end
230
+
231
+ describe "checking whether a key exists" do
232
+ it "should return true if the object does exist" do
233
+ @client.http.should_receive(:head).and_return(:code => 200)
234
+ @bucket.exists?("foo").should be_true
235
+ end
236
+
237
+ it "should return false if the object doesn't exist" do
238
+ @client.http.should_receive(:head).and_return(:code => 404)
239
+ @bucket.exists?("foo").should be_false
240
+ end
241
+ end
242
+
243
+ it "should delete a key from within the bucket" do
244
+ @client.http.should_receive(:delete).with([204,404], @client.prefix, @bucket.name, 'bar',{},{}).and_return(:code => 204)
245
+ @bucket.delete('bar')
246
+ end
247
+ end
@@ -0,0 +1,174 @@
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::Client do
17
+ describe "when initializing" do
18
+ it "should default to the local interface on port 8098" do
19
+ client = Riak::Client.new
20
+ client.host.should == "127.0.0.1"
21
+ client.port.should == 8098
22
+ end
23
+
24
+ it "should accept a host" do
25
+ client = Riak::Client.new :host => "riak.basho.com"
26
+ client.host.should == "riak.basho.com"
27
+ end
28
+
29
+ it "should accept a port" do
30
+ client = Riak::Client.new :port => 9000
31
+ client.port.should == 9000
32
+ end
33
+
34
+ it "should accept a client ID" do
35
+ client = Riak::Client.new :client_id => "AAAAAA=="
36
+ client.client_id.should == "AAAAAA=="
37
+ end
38
+
39
+ it "should turn an integer client ID into a base64-encoded string" do
40
+ client = Riak::Client.new :client_id => 1
41
+ client.client_id.should == "AAAAAQ=="
42
+ end
43
+
44
+ it "should create a client ID if not specified" do
45
+ Riak::Client.new.client_id.should be_kind_of(String)
46
+ end
47
+
48
+ it "should accept a path prefix" do
49
+ client = Riak::Client.new(:prefix => "/jiak/")
50
+ client.prefix.should == "/jiak/"
51
+ end
52
+
53
+ it "should default the prefix to /riak/ if not specified" do
54
+ Riak::Client.new.prefix.should == "/riak/"
55
+ end
56
+
57
+ it "should accept a mapreduce path" do
58
+ client = Riak::Client.new(:mapred => "/mr")
59
+ client.mapred.should == "/mr"
60
+ end
61
+
62
+ it "should default the mapreduce path to /mapred if not specified" do
63
+ Riak::Client.new.mapred.should == "/mapred"
64
+ end
65
+ end
66
+
67
+ describe "reconfiguring" do
68
+ before :each do
69
+ @client = Riak::Client.new
70
+ end
71
+
72
+ describe "setting the host" do
73
+ it "should allow setting the host" do
74
+ @client.should respond_to(:host=)
75
+ @client.host = "riak.basho.com"
76
+ @client.host.should == "riak.basho.com"
77
+ end
78
+
79
+ it "should require the host to be an IP or hostname" do
80
+ [238472384972, ""].each do |invalid|
81
+ lambda { @client.host = invalid }.should raise_error(ArgumentError)
82
+ end
83
+ ["127.0.0.1", "10.0.100.5", "localhost", "otherhost.local", "riak.basho.com"].each do |valid|
84
+ lambda { @client.host = valid }.should_not raise_error
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "setting the port" do
90
+ it "should allow setting the port" do
91
+ @client.should respond_to(:port=)
92
+ @client.port = 9000
93
+ @client.port.should == 9000
94
+ end
95
+
96
+ it "should require the port to be a valid number" do
97
+ [-1,65536,"foo"].each do |invalid|
98
+ lambda { @client.port = invalid }.should raise_error(ArgumentError)
99
+ end
100
+ [0,1,65535,8098].each do |valid|
101
+ lambda { @client.port = valid }.should_not raise_error
102
+ end
103
+ end
104
+ end
105
+
106
+ it "should allow setting the prefix (although we prefer the raw interface)" do
107
+ @client.should respond_to(:prefix=)
108
+ @client.prefix = "/another-prefix"
109
+ @client.prefix.should == "/another-prefix"
110
+ end
111
+
112
+ describe "setting the client id" do
113
+ it "should accept a string unmodified" do
114
+ @client.client_id = "foo"
115
+ @client.client_id.should == "foo"
116
+ end
117
+
118
+ it "should base64-encode an integer" do
119
+ @client.client_id = 1
120
+ @client.client_id.should == "AAAAAQ=="
121
+ end
122
+
123
+ it "should reject an integer equal to the maximum client id" do
124
+ lambda { @client.client_id = Riak::Client::MAX_CLIENT_ID }.should raise_error(ArgumentError)
125
+ end
126
+
127
+ it "should reject an integer larger than the maximum client id" do
128
+ lambda { @client.client_id = Riak::Client::MAX_CLIENT_ID + 1 }.should raise_error(ArgumentError)
129
+ end
130
+ end
131
+ end
132
+
133
+ describe "choosing an HTTP backend" do
134
+ before :each do
135
+ @client = Riak::Client.new
136
+ end
137
+
138
+ it "should choose the Curb backend if Curb is available" do
139
+ @client.should_receive(:require).with('curb').and_return(true)
140
+ @client.http.should be_instance_of(Riak::Client::CurbBackend)
141
+ end
142
+
143
+ it "should choose the Net::HTTP backend if Curb is unavailable" do
144
+ @client.should_receive(:require).with('curb').and_raise(LoadError)
145
+ @client.should_receive(:warn).and_return(true)
146
+ @client.http.should be_instance_of(Riak::Client::NetHTTPBackend)
147
+ end
148
+ end
149
+
150
+ describe "retrieving a bucket" do
151
+ before :each do
152
+ @client = Riak::Client.new
153
+ @http = mock(Riak::Client::HTTPBackend)
154
+ @client.stub!(:http).and_return(@http)
155
+ @payload = {:headers => {"content-type" => ["application/json"]}, :body => "{}"}
156
+ @http.stub!(:get).and_return(@payload)
157
+ end
158
+
159
+ it "should send a GET request to the bucket name and return a Riak::Bucket" do
160
+ @http.should_receive(:get).with(200, "/riak/", "foo", {}, {}).and_return(@payload)
161
+ @client.bucket("foo").should be_kind_of(Riak::Bucket)
162
+ end
163
+
164
+ it "should allow requesting bucket properties without the keys" do
165
+ @http.should_receive(:get).with(200, "/riak/", "foo", {:keys => false}, {}).and_return(@payload)
166
+ @client.bucket("foo", :keys => false)
167
+ end
168
+
169
+ it "should escape bucket names with invalid characters" do
170
+ @http.should_receive(:get).with(200, "/riak/", "foo%2Fbar%20", {:keys => false}, {}).and_return(@payload)
171
+ @client.bucket("foo/bar ", :keys => false)
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,53 @@
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
+ begin
17
+ require 'curb'
18
+ rescue LoadError
19
+ warn "Skipping CurbBackend specs, curb library not found."
20
+ else
21
+ $server = MockServer.new
22
+ at_exit { $server.stop }
23
+
24
+ describe Riak::Client::CurbBackend do
25
+ def setup_http_mock(method, uri, options={})
26
+ method = method.to_s.upcase
27
+ uri = URI.parse(uri)
28
+ path = uri.path || "/"
29
+ query = uri.query || ""
30
+ status = options[:status] ? Array(options[:status]).first.to_i : 200
31
+ body = options[:body] || []
32
+ headers = options[:headers] || {}
33
+ headers['Content-Type'] ||= "text/plain"
34
+ $server.attach do |env|
35
+ env["REQUEST_METHOD"].should == method
36
+ env["PATH_INFO"].should == path
37
+ env["QUERY_STRING"].should == query
38
+ [status, headers, Array(body)]
39
+ end
40
+ end
41
+
42
+ before :each do
43
+ @client = Riak::Client.new(:port => $server.port) # Point to our mock
44
+ @backend = Riak::Client::CurbBackend.new(@client)
45
+ end
46
+
47
+ it_should_behave_like "HTTP backend"
48
+
49
+ after :each do
50
+ $server.detach
51
+ end
52
+ end
53
+ end