voldemort-rb 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
File without changes
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Alejandro Crosa
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ Voldemort-client
2
+ ================
3
+
4
+ # Requirements
5
+
6
+ Since the communication between the client and the server is done using protocol buffers you'll need the ruby_protobuf gem found at http://code.google.com/p/ruby-protobuf/.
7
+
8
+ Examples
9
+ =======
10
+
11
+ # Basic Usage
12
+ ## Connecting and bootstrapping
13
+
14
+ client = VoldemortClient.new("test", "localhost:6666")
15
+
16
+ ## Storing a value
17
+
18
+ client.put("some key", "some value")
19
+
20
+ ## Reading a value
21
+
22
+ client.get("some key")
23
+
24
+ you'll get
25
+
26
+ => some value
27
+
28
+ ## deleting a value from a key
29
+
30
+ client.delete("some key")
31
+
32
+ # Conflict resolution
33
+ ## Default
34
+
35
+ Voldemort replies with versions of a value, it's up to the client to resolve the conflicts. By default the library will return the version that's most recent.
36
+
37
+ ## Custom
38
+
39
+ You can override the default behavior and perform a custom resolution of the conflict, here's how to do so:
40
+
41
+ client = VoldemortClient.new("test", "localhost:6666") do |versions|
42
+ versions.first # just return the first version for example
43
+ end
44
+
45
+
46
+ Copyright (c) 2010 Alejandro Crosa, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'spec/rake/spectask'
6
+
7
+ GEM = 'Voldemort Client'
8
+ GEM_NAME = 'voldemort_client'
9
+ GEM_VERSION = '0.1'
10
+ AUTHORS = ['Alejandro Crosa']
11
+ EMAIL = "alejandrocrosa@gmail.com"
12
+ HOMEPAGE = "http://github.com/acrosa/Voldemort-Ruby-Client"
13
+ SUMMARY = "A Ruby client for the Voldemort distributed key value store"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = GEM
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["LICENSE"]
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.authors = AUTHORS
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+ s.add_development_dependency "rspec"
27
+ s.require_path = 'lib'
28
+ s.autorequire = GEM
29
+ s.files = %w(LICENSE README.md Rakefile) + Dir.glob("{lib,tasks,spec}/**/*")
30
+ end
31
+
32
+ task :default => :spec
33
+
34
+ desc "Run specs"
35
+ Spec::Rake::SpecTask.new do |t|
36
+ t.spec_files = FileList['spec/**/*_spec.rb']
37
+ t.spec_opts = %w(-fs --color)
38
+ end
39
+
40
+ Rake::GemPackageTask.new(spec) do |pkg|
41
+ pkg.gem_spec = spec
42
+ end
43
+
44
+ desc "install the gem locally"
45
+ task :install => [:package] do
46
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
47
+ end
48
+
49
+ desc "create a gemspec file"
50
+ task :make_spec do
51
+ File.open("#{GEM}.gemspec", "w") do |file|
52
+ file.puts spec.to_ruby
53
+ end
54
+ end
55
+
56
+ desc "Run all examples with RCov"
57
+ Spec::Rake::SpecTask.new(:rcov) do |t|
58
+ t.spec_files = FileList['spec/**/*_spec.rb']
59
+ t.rcov = true
60
+ end
@@ -0,0 +1,115 @@
1
+ require 'rexml/document'
2
+
3
+ class Connection
4
+ include REXML
5
+
6
+ attr_accessor :hosts # The hosts from where we bootstrapped.
7
+ attr_accessor :nodes # The array of VoldemortNodes available.
8
+ attr_accessor :db_name # The DB store name.
9
+ attr_accessor :connected_node # The VoldemortNode we are connected to.
10
+ attr_accessor :request_count # Used to track the number of request a node receives.
11
+ attr_accessor :request_limit_per_node # Limit the number of request per node.
12
+
13
+ STATUS_OK = "ok"
14
+ PROTOCOL = "pb0"
15
+ DEFAULT_REQUEST_LIMIT_PER_NODE = 500
16
+
17
+ def initialize(db_name, hosts, request_limit_per_node = DEFAULT_REQUEST_LIMIT_PER_NODE)
18
+ self.db_name = db_name
19
+ self.hosts = hosts
20
+ self.nodes = hosts.collect{ |h|
21
+ n = h.split(":")
22
+ node = VoldemortNode.new
23
+ node.host = n[0]
24
+ node.port = n[1]
25
+ node
26
+ }
27
+ self.request_count = 0
28
+ self.request_limit_per_node = request_limit_per_node
29
+ end
30
+
31
+ def bootstrap
32
+ response = self.get_from("metadata", "cluster.xml", false)
33
+ xml = response[1][0][1]
34
+ self.nodes = self.parse_nodes_from(xml)
35
+ self.connect_to_random_node
36
+ rescue StandardError => e
37
+ raise("There was an error trying to bootstrap from the specified servers: #{e}")
38
+ end
39
+
40
+ def connect_to_random_node
41
+ nodes = self.nodes.sort_by { rand }
42
+ for node in nodes do
43
+ if self.connect_to(node.host, node.port)
44
+ self.connected_node = node
45
+ self.request_count = 0
46
+ return node
47
+ end
48
+ end
49
+ end
50
+
51
+ def parse_nodes_from(xml)
52
+ nodes = []
53
+ doc = REXML::Document.new(xml)
54
+ XPath.each(doc, "/cluster/server").each do |n|
55
+ node = VoldemortNode.new
56
+ node.id = n.elements["id"].text
57
+ node.host = n.elements["host"].text
58
+ node.port = n.elements["socket-port"].text
59
+ node.http_port = n.elements["http-port"].text
60
+ node.admin_port = n.elements["admin-port"].text
61
+ node.partitions = n.elements["partitions"].text
62
+ nodes << node
63
+ end
64
+ nodes
65
+ end
66
+
67
+ def protocol_version
68
+ PROTOCOL
69
+ end
70
+
71
+ def connect
72
+ self.connect!
73
+ end
74
+
75
+ def reconnect
76
+ self.reconnect!
77
+ end
78
+
79
+ def disconnect
80
+ self.disconnect!
81
+ end
82
+
83
+ def reconnect_when_errors_in(response = nil)
84
+ return unless response
85
+ self.reconnect! if response.error
86
+ end
87
+
88
+ def rebalance_connection?
89
+ self.request_count >= self.request_limit_per_node
90
+ end
91
+
92
+ def rebalance_connection_if_needed
93
+ self.reconnect if self.rebalance_connection?
94
+ self.request_count += 1
95
+ end
96
+
97
+ def get(key)
98
+ self.rebalance_connection_if_needed
99
+ self.get_from(self.db_name, key, true)
100
+ end
101
+
102
+ def get_all(keys)
103
+ self.rebalance_connection_if_needed
104
+ self.get_all_from(self.db_name, keys, true)
105
+ end
106
+
107
+ def put(key, value, version = nil, route = true)
108
+ self.rebalance_connection_if_needed
109
+ self.put_from(self.db_name, key, value, version, route)
110
+ end
111
+
112
+ def delete(key)
113
+ self.delete_from(self.db_name, key)
114
+ end
115
+ end
@@ -0,0 +1,171 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+
4
+ require File.join(File.dirname(__FILE__), "..", "protos", "voldemort-client.pb")
5
+
6
+ class TCPConnection < Connection
7
+ include Voldemort
8
+
9
+ attr_accessor :socket
10
+
11
+ SOCKET_TIMEOUT = 3
12
+
13
+ def connect_to(host, port)
14
+ begin
15
+ timeout(SOCKET_TIMEOUT) do
16
+ self.socket = TCPSocket.open(host, port)
17
+ self.send_protocol_version
18
+ if(protocol_handshake_ok?)
19
+ return self.socket
20
+ else
21
+ raise "There was an error connecting to the node"
22
+ end
23
+ end
24
+ rescue Timeout::Error
25
+ raise "Timeout when connecting to node"
26
+ rescue
27
+ false
28
+ end
29
+ end
30
+
31
+ # performs a get using the specified parameters
32
+ #
33
+ def get_from(db_name, key, route = true)
34
+ request = VoldemortRequest.new
35
+ request.should_route = route
36
+ request.store = db_name
37
+ request.type = RequestType::GET
38
+ request.get = GetRequest.new
39
+ request.get.key = key
40
+
41
+ self.send(request) # send the request
42
+ raw_response = self.receive # read the response
43
+ response = GetResponse.new.parse_from_string(raw_response) # compose the get object based on the raw response
44
+ reconnect_when_errors_in(response)
45
+ response
46
+ end
47
+
48
+ # performs a get using multiple keys
49
+ #
50
+ def get_all_from(db_name, keys, route = true)
51
+ request = VoldemortRequest.new
52
+ request.should_route = route
53
+ request.store = db_name
54
+ request.type = RequestType::GET_ALL
55
+ request.getAll = GetAllRequest.new
56
+ request.getAll.keys = keys
57
+
58
+ self.send(request) # send the request
59
+ raw_response = self.receive # read the response
60
+ response = GetAllResponse.new.parse_from_string(raw_response) # compose the get object based on the raw response
61
+ reconnect_when_errors_in(response)
62
+ response
63
+ end
64
+
65
+ def put_from(db_name, key, value, version = nil, route = true)
66
+ version = get_version(key) unless version
67
+ request = VoldemortRequest.new
68
+ request.should_route = route
69
+ request.store = db_name
70
+ request.type = RequestType::PUT
71
+ request.put = PutRequest.new
72
+ request.put.key = key
73
+ request.put.versioned = Versioned.new
74
+ request.put.versioned.value = value
75
+ request.put.versioned.version = VectorClock.new
76
+ request.put.versioned.version.merge_from(version)
77
+
78
+ self.send(request) # send the request
79
+ raw_response = self.receive # read the response
80
+ response = PutResponse.new.parse_from_string(raw_response)
81
+ reconnect_when_errors_in(response)
82
+
83
+ add_to_versions(version) # add version or increment when needed
84
+ version
85
+ end
86
+
87
+ def delete_from(db_name, key, version = nil, route = true)
88
+ version = get_version(key) unless version
89
+ request = VoldemortRequest.new
90
+ request.should_route = route
91
+ request.store = db_name
92
+ request.type = RequestType::DELETE
93
+ request.delete = DeleteRequest.new
94
+ request.delete.key = key
95
+ request.delete.version = VectorClock.new
96
+ request.delete.version.merge_from(version)
97
+
98
+ self.send(request) # send the request
99
+ raw_response = self.receive # read the response
100
+ response = DeleteResponse.new.parse_from_string(raw_response)
101
+ reconnect_when_errors_in(response)
102
+ response.success
103
+ end
104
+
105
+ def add_to_versions(version)
106
+ entry = version.entries.detect { |e| e.node_id == self.connected_node.id.to_i }
107
+ if(entry)
108
+ entry.version += 1
109
+ else
110
+ entry = ClockEntry.new
111
+ entry.node_id = self.connected_node.id.to_i
112
+ entry.version = 1
113
+ version.entries << entry
114
+ version.timestamp = Time.new.to_i * 1000
115
+ end
116
+ version
117
+ end
118
+
119
+ def get_version(key)
120
+ other_version = get(key)[1][0]
121
+ if(other_version)
122
+ return other_version.version
123
+ else
124
+ version = VectorClock.new
125
+ version.timestamp = Time.new.to_i * 1000
126
+ return version
127
+ end
128
+ end
129
+
130
+ # unpack argument is N | Long, network (big-endian) byte order.
131
+ # from http://ruby-doc.org/doxygen/1.8.4/pack_8c-source.html
132
+ def receive
133
+ raw_size = self.socket.recv(4)
134
+ size = raw_size.unpack('N')
135
+ self.socket.recv(size[0])
136
+ rescue
137
+ self.reconnect!
138
+ end
139
+
140
+ # pack argument is N | Long, network (big-endian) byte order.
141
+ # from http://ruby-doc.org/doxygen/1.8.4/pack_8c-source.html
142
+ def send(request)
143
+ self.reconnect unless self.socket
144
+ bytes = request.serialize_to_string # helper method thanks to ruby-protobuf
145
+ self.socket.write([bytes.size].pack("N") + bytes)
146
+ rescue
147
+ self.disconnect!
148
+ end
149
+
150
+ def send_protocol_version
151
+ self.socket.write(self.protocol_version)
152
+ end
153
+
154
+ def protocol_handshake_ok?
155
+ self.socket.recv(2) == STATUS_OK
156
+ end
157
+
158
+ def connect!
159
+ self.connect_to_random_node
160
+ end
161
+
162
+ def reconnect!
163
+ self.disconnect! if self.socket
164
+ self.connect!
165
+ end
166
+
167
+ def disconnect!
168
+ self.socket.close if self.socket
169
+ self.socket = nil
170
+ end
171
+ end
@@ -0,0 +1,3 @@
1
+ class VoldemortNode
2
+ attr_accessor :id, :host, :port, :http_port, :admin_port, :partitions
3
+ end
@@ -0,0 +1,190 @@
1
+ ### Generated by rprotoc. DO NOT EDIT!
2
+ ### <proto file: voldemort-client.proto>
3
+ # package voldemort;
4
+ #
5
+ # option java_package = "voldemort.client.protocol.pb";
6
+ # option java_outer_classname = "VProto";
7
+ # option optimize_for = SPEED;
8
+ #
9
+ # message ClockEntry {
10
+ # required int32 node_id = 1;
11
+ # required int64 version = 2;
12
+ # }
13
+ #
14
+ # message VectorClock {
15
+ # repeated ClockEntry entries = 1;
16
+ # optional int64 timestamp = 2;
17
+ # }
18
+ #
19
+ # message Versioned {
20
+ # required bytes value = 1;
21
+ # required VectorClock version = 2;
22
+ # }
23
+ #
24
+ # message Error {
25
+ # required int32 error_code = 1;
26
+ # required string error_message = 2;
27
+ # }
28
+ #
29
+ # message KeyedVersions {
30
+ # required bytes key = 1;
31
+ # repeated Versioned versions = 2;
32
+ # }
33
+ #
34
+ # message GetRequest {
35
+ # optional bytes key = 1;
36
+ # }
37
+ #
38
+ # message GetResponse {
39
+ # repeated Versioned versioned = 1;
40
+ # optional Error error = 2;
41
+ # }
42
+ #
43
+ # message GetVersionResponse {
44
+ # repeated VectorClock versions = 1;
45
+ # optional Error error = 2;
46
+ # }
47
+ #
48
+ # message GetAllRequest {
49
+ # repeated bytes keys = 1;
50
+ # }
51
+ #
52
+ # message GetAllResponse {
53
+ # repeated KeyedVersions values = 1;
54
+ # optional Error error = 2;
55
+ # }
56
+ #
57
+ # message PutRequest {
58
+ # required bytes key = 1;
59
+ # required Versioned versioned = 2;
60
+ # }
61
+ #
62
+ # message PutResponse {
63
+ # optional Error error = 1;
64
+ # }
65
+ #
66
+ # message DeleteRequest {
67
+ # required bytes key = 1;
68
+ # required VectorClock version = 2;
69
+ # }
70
+ #
71
+ # message DeleteResponse {
72
+ # required bool success = 1;
73
+ # optional Error error = 2;
74
+ # }
75
+ #
76
+ # enum RequestType {
77
+ # GET = 0;
78
+ # GET_ALL = 1;
79
+ # PUT = 2;
80
+ # DELETE = 3;
81
+ # GET_VERSION = 4;
82
+ # }
83
+ #
84
+ #
85
+ # message VoldemortRequest {
86
+ # required RequestType type = 1;
87
+ # required bool should_route = 2 [default = false];
88
+ # required string store = 3;
89
+ # optional GetRequest get = 4;
90
+ # optional GetAllRequest getAll = 5;
91
+ # optional PutRequest put = 6;
92
+ # optional DeleteRequest delete = 7;
93
+ # optional int32 requestRouteType = 8;
94
+ # }
95
+ require 'protobuf/message/message'
96
+ require 'protobuf/message/enum'
97
+ require 'protobuf/message/service'
98
+ require 'protobuf/message/extend'
99
+
100
+ module Voldemort
101
+ ::Protobuf::OPTIONS[:"java_package"] = "voldemort.client.protocol.pb"
102
+ ::Protobuf::OPTIONS[:"java_outer_classname"] = "VProto"
103
+ ::Protobuf::OPTIONS[:"optimize_for"] = :SPEED
104
+ class ClockEntry < ::Protobuf::Message
105
+ defined_in __FILE__
106
+ required :int32, :node_id, 1
107
+ required :int64, :version, 2
108
+ end
109
+ class VectorClock < ::Protobuf::Message
110
+ defined_in __FILE__
111
+ repeated :ClockEntry, :entries, 1
112
+ optional :int64, :timestamp, 2
113
+ end
114
+ class Versioned < ::Protobuf::Message
115
+ defined_in __FILE__
116
+ required :bytes, :value, 1
117
+ required :VectorClock, :version, 2
118
+ end
119
+ class Error < ::Protobuf::Message
120
+ defined_in __FILE__
121
+ required :int32, :error_code, 1
122
+ required :string, :error_message, 2
123
+ end
124
+ class KeyedVersions < ::Protobuf::Message
125
+ defined_in __FILE__
126
+ required :bytes, :key, 1
127
+ repeated :Versioned, :versions, 2
128
+ end
129
+ class GetRequest < ::Protobuf::Message
130
+ defined_in __FILE__
131
+ optional :bytes, :key, 1
132
+ end
133
+ class GetResponse < ::Protobuf::Message
134
+ defined_in __FILE__
135
+ repeated :Versioned, :versioned, 1
136
+ optional :Error, :error, 2
137
+ end
138
+ class GetVersionResponse < ::Protobuf::Message
139
+ defined_in __FILE__
140
+ repeated :VectorClock, :versions, 1
141
+ optional :Error, :error, 2
142
+ end
143
+ class GetAllRequest < ::Protobuf::Message
144
+ defined_in __FILE__
145
+ repeated :bytes, :keys, 1
146
+ end
147
+ class GetAllResponse < ::Protobuf::Message
148
+ defined_in __FILE__
149
+ repeated :KeyedVersions, :values, 1
150
+ optional :Error, :error, 2
151
+ end
152
+ class PutRequest < ::Protobuf::Message
153
+ defined_in __FILE__
154
+ required :bytes, :key, 1
155
+ required :Versioned, :versioned, 2
156
+ end
157
+ class PutResponse < ::Protobuf::Message
158
+ defined_in __FILE__
159
+ optional :Error, :error, 1
160
+ end
161
+ class DeleteRequest < ::Protobuf::Message
162
+ defined_in __FILE__
163
+ required :bytes, :key, 1
164
+ required :VectorClock, :version, 2
165
+ end
166
+ class DeleteResponse < ::Protobuf::Message
167
+ defined_in __FILE__
168
+ required :bool, :success, 1
169
+ optional :Error, :error, 2
170
+ end
171
+ class RequestType < ::Protobuf::Enum
172
+ defined_in __FILE__
173
+ GET = 0
174
+ GET_ALL = 1
175
+ PUT = 2
176
+ DELETE = 3
177
+ GET_VERSION = 4
178
+ end
179
+ class VoldemortRequest < ::Protobuf::Message
180
+ defined_in __FILE__
181
+ required :RequestType, :type, 1
182
+ required :bool, :should_route, 2, :default => false
183
+ required :string, :store, 3
184
+ optional :GetRequest, :get, 4
185
+ optional :GetAllRequest, :getAll, 5
186
+ optional :PutRequest, :put, 6
187
+ optional :DeleteRequest, :delete, 7
188
+ optional :int32, :requestRouteType, 8
189
+ end
190
+ end
@@ -0,0 +1,92 @@
1
+ package voldemort;
2
+
3
+ option java_package = "voldemort.client.protocol.pb";
4
+ option java_outer_classname = "VProto";
5
+ option optimize_for = SPEED;
6
+
7
+ message ClockEntry {
8
+ required int32 node_id = 1;
9
+ required int64 version = 2;
10
+ }
11
+
12
+ message VectorClock {
13
+ repeated ClockEntry entries = 1;
14
+ optional int64 timestamp = 2;
15
+ }
16
+
17
+ message Versioned {
18
+ required bytes value = 1;
19
+ required VectorClock version = 2;
20
+ }
21
+
22
+ message Error {
23
+ required int32 error_code = 1;
24
+ required string error_message = 2;
25
+ }
26
+
27
+ message KeyedVersions {
28
+ required bytes key = 1;
29
+ repeated Versioned versions = 2;
30
+ }
31
+
32
+ message GetRequest {
33
+ optional bytes key = 1;
34
+ }
35
+
36
+ message GetResponse {
37
+ repeated Versioned versioned = 1;
38
+ optional Error error = 2;
39
+ }
40
+
41
+ message GetVersionResponse {
42
+ repeated VectorClock versions = 1;
43
+ optional Error error = 2;
44
+ }
45
+
46
+ message GetAllRequest {
47
+ repeated bytes keys = 1;
48
+ }
49
+
50
+ message GetAllResponse {
51
+ repeated KeyedVersions values = 1;
52
+ optional Error error = 2;
53
+ }
54
+
55
+ message PutRequest {
56
+ required bytes key = 1;
57
+ required Versioned versioned = 2;
58
+ }
59
+
60
+ message PutResponse {
61
+ optional Error error = 1;
62
+ }
63
+
64
+ message DeleteRequest {
65
+ required bytes key = 1;
66
+ required VectorClock version = 2;
67
+ }
68
+
69
+ message DeleteResponse {
70
+ required bool success = 1;
71
+ optional Error error = 2;
72
+ }
73
+
74
+ enum RequestType {
75
+ GET = 0;
76
+ GET_ALL = 1;
77
+ PUT = 2;
78
+ DELETE = 3;
79
+ GET_VERSION = 4;
80
+ }
81
+
82
+
83
+ message VoldemortRequest {
84
+ required RequestType type = 1;
85
+ required bool should_route = 2 [default = false];
86
+ required string store = 3;
87
+ optional GetRequest get = 4;
88
+ optional GetAllRequest getAll = 5;
89
+ optional PutRequest put = 6;
90
+ optional DeleteRequest delete = 7;
91
+ optional int32 requestRouteType = 8;
92
+ }
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(__FILE__), "connection", "voldemort_node")
2
+ require File.join(File.dirname(__FILE__), "connection", "connection")
3
+ require File.join(File.dirname(__FILE__), "connection", "tcp_connection")
4
+
5
+ class VoldemortClient
6
+ attr_accessor :connection
7
+ attr_accessor :conflict_resolver
8
+
9
+ def initialize(db_name, *hosts, &block)
10
+ self.conflict_resolver = block unless !block
11
+ self.connection = TCPConnection.new(db_name, hosts) # implement and modifiy if you don't want to use TCP protobuf.
12
+ self.connection.bootstrap
13
+ end
14
+
15
+ def get(key)
16
+ versions = self.connection.get(key)
17
+ version = self.resolve_conflicts(versions.versioned)
18
+ if version
19
+ version.value
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ def get_all(keys)
26
+ all_version = self.connection.get_all(keys)
27
+ values = {}
28
+ all_version.values.collect do |v|
29
+ values[v.key] = self.resolve_conflicts(v.versions).value
30
+ end
31
+ values
32
+ end
33
+
34
+ def put(key, value, version = nil)
35
+ self.connection.put(key, value)
36
+ end
37
+
38
+ def delete(key)
39
+ self.connection.delete(key)
40
+ end
41
+
42
+ def resolve_conflicts(versions)
43
+ return self.conflict_resolver.call(versions) if self.conflict_resolver
44
+ # by default just return the version that has the most recent timestamp.
45
+ versions.max { |a, b| a.version.timestamp <=> b.version.timestamp }
46
+ end
47
+ end
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Connection do
4
+
5
+ before(:each) do
6
+ @connection = Connection.new("test", "localhost:6666")
7
+ end
8
+
9
+ describe "default methods" do
10
+
11
+ it "should support connect" do
12
+ @connection.should respond_to(:connect)
13
+ end
14
+
15
+ it "should support reconnect" do
16
+ @connection.should respond_to(:reconnect)
17
+ end
18
+
19
+ it "should support disconnect" do
20
+ @connection.should respond_to(:disconnect)
21
+ end
22
+
23
+ it "should parse nodes from xml" do
24
+ @connection.should respond_to(:parse_nodes_from)
25
+ xml = "<cluster>\r\n <name>mycluster</name>\r\n <server>\r\n <id>0</id>\r\n <host>localhost</host>\r\n <http-port>8081</http-port>\r\n <socket-port>6666</socket-port>\r\n <admin-port>6667</admin-port>\r\n <partitions>0, 1</partitions>\r\n </server>\r\n</cluster>"
26
+ nodes = @connection.parse_nodes_from(xml)
27
+ nodes.first.host.should eql("localhost")
28
+ nodes.first.port.should eql("6666")
29
+ nodes.length.should eql(1)
30
+ end
31
+
32
+ it "should tell to wich node is connected to" do
33
+ @connection.should respond_to(:connected_node)
34
+ node = mock(VoldemortNode)
35
+ node.stub!(:host).and_return("localhost")
36
+ node.stub!(:port).and_return(6666)
37
+ @connection.nodes.stub!(:sort_by).and_return([node])
38
+ @connection.stub!(:connect_to).and_return(true)
39
+ @connection.connect_to_random_node
40
+ @connection.connected_node.should eql(node)
41
+ end
42
+
43
+ it "should use protobuf by default" do
44
+ @connection.protocol_version.should eql("pb0")
45
+ end
46
+
47
+ it "should use the hosts specified" do
48
+ connection = Connection.new("test", "localhost:6666")
49
+ connection.hosts.should eql("localhost:6666")
50
+ connection.nodes.length.should eql(1)
51
+ connection2 = Connection.new("test", ["localhost:6666", "localhost:7777"])
52
+ connection2.hosts.should eql(["localhost:6666", "localhost:7777"])
53
+ connection2.nodes.length.should eql(2)
54
+ end
55
+ end
56
+
57
+ describe "rebalance nodes by evaluating number of requests" do
58
+
59
+ it "should have a request_count and request_limit_per_node per node connection" do
60
+ @connection.should respond_to(:request_count)
61
+ @connection.should respond_to(:request_limit_per_node)
62
+ end
63
+
64
+ it "should tell if the request limit per node was reached" do
65
+ @connection.request_count = 0
66
+ @connection.request_limit_per_node = 10
67
+ @connection.rebalance_connection?.should eql(false)
68
+ @connection.request_count = 11
69
+ @connection.request_limit_per_node = 10
70
+ @connection.rebalance_connection?.should eql(true)
71
+ end
72
+
73
+ it "should reconnect every N number of requests" do
74
+ @connection.should_receive(:rebalance_connection?).and_return(true)
75
+ @connection.should_receive(:reconnect).and_return(true)
76
+ @connection.rebalance_connection_if_needed
77
+ end
78
+
79
+ it "should not reconnect if it haven't reached the limit of requests" do
80
+ @connection.should_receive(:rebalance_connection?).and_return(false)
81
+ @connection.should_not_receive(:reconnect).and_return(false)
82
+ @connection.rebalance_connection_if_needed
83
+ end
84
+
85
+ it "should rebalance if needed when calling get, get_all or put" do
86
+ @connection.should_receive(:rebalance_connection_if_needed).exactly(3).times.and_return(true)
87
+ @connection.stub!(:get_from).and_return(true)
88
+ @connection.stub!(:get_all_from).and_return(true)
89
+ @connection.stub!(:put_from).and_return(true)
90
+ @connection.stub!(:delete_from).and_return(true)
91
+ @connection.get("value")
92
+ @connection.put("value", "value")
93
+ @connection.get_all(["key1", "key2"])
94
+ @connection.delete("key")
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,2 @@
1
+ require 'rubygems'
2
+ require 'voldemort_client'
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe TCPConnection do
4
+
5
+ before(:each) do
6
+ @connection = TCPConnection.new("test", "localhost:6666")
7
+ end
8
+
9
+ describe "connection mechanism" do
10
+
11
+ it "should connect to a specified host" do
12
+ @connection.should respond_to(:connect_to)
13
+ mock_socket = mock(TCPSocket)
14
+ TCPSocket.should_receive(:open).and_return(mock_socket)
15
+ @connection.should_receive(:send_protocol_version).and_return(true)
16
+ @connection.should_receive(:protocol_handshake_ok?).and_return(true)
17
+ @connection.connect_to("localhost", 6666).should eql(mock_socket)
18
+ end
19
+
20
+ it "should send the protocol" do
21
+ @connection.should respond_to(:send_protocol_version)
22
+ mock_socket = mock(TCPSocket)
23
+ @connection.stub!(:socket).and_return(mock_socket)
24
+ mock_socket.should_receive(:write).with(Connection::PROTOCOL).and_return(true)
25
+ @connection.send_protocol_version.should eql(true)
26
+ end
27
+
28
+ it "should receive the protocol handshake response" do
29
+ @connection.should respond_to(:protocol_handshake_ok?)
30
+ mock_socket = mock(TCPSocket)
31
+ @connection.stub!(:socket).and_return(mock_socket)
32
+ mock_socket.should_receive(:recv).with(2).and_return(Connection::STATUS_OK)
33
+ @connection.protocol_handshake_ok?.should eql(true)
34
+ end
35
+
36
+ it "should have a socket" do
37
+ @connection.should respond_to(:socket)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'lib/protos/voldemort-client.pb'
3
+
4
+ include Voldemort
5
+
6
+ describe VoldemortClient do
7
+
8
+ before(:each) do
9
+ connection = mock(TCPConnection)
10
+ node = mock(VoldemortNode)
11
+ connection.stub!(:bootstrap).and_return(node)
12
+ TCPConnection.stub!(:new).and_return(connection)
13
+ @client = VoldemortClient.new("test", "localhost:6666")
14
+ @client.stub!(:connection).and_return(connection)
15
+ end
16
+
17
+ describe "connection abstraction" do
18
+ it "should have a connection" do
19
+ @client.should respond_to(:connection)
20
+ end
21
+
22
+ it "should initialize the connection" do
23
+ @client.connection.should_not be(nil)
24
+ end
25
+ end
26
+
27
+ describe "default methods" do
28
+
29
+ it "should support get" do
30
+ @client.should respond_to(:get)
31
+ version = mock(Versioned)
32
+ v = mock(VectorClock)
33
+ v.stub!(:value).and_return("some value")
34
+ version.stub!(:versioned).and_return([v])
35
+ @client.connection.should_receive(:get).with("key").and_return(version)
36
+ @client.get("key").should eql("some value")
37
+ end
38
+
39
+ it "should support get all" do
40
+ @client.should respond_to(:get_all)
41
+ version = mock(Versioned)
42
+ v = mock(VectorClock)
43
+ v.stub!(:value).and_return("some value")
44
+ v.stub!(:key).and_return("key")
45
+ v.stub!(:versions).and_return([v])
46
+ version.stub!(:values).and_return([v])
47
+ @client.connection.should_receive(:get_all).with(["key", "key2"]).and_return(version)
48
+ @client.get_all(["key", "key2"]).should eql({ "key" => "some value" }) # we pretend key2 doesn't exist
49
+ end
50
+
51
+ it "should support put" do
52
+ @client.should respond_to(:put)
53
+ @client.connection.should_receive(:put).with("key", "value").and_return("version")
54
+ @client.put("key", "value").should eql("version")
55
+ end
56
+
57
+ it "should support delete" do
58
+ @client.should respond_to(:delete)
59
+ @client.connection.should_receive(:delete).with("key").and_return(true)
60
+ @client.delete("key").should eql(true)
61
+ end
62
+ end
63
+
64
+ describe "default resolver" do
65
+
66
+ before(:each) do
67
+ @old_versioned = Versioned.new
68
+ @old_versioned.value = "old value"
69
+ @old_versioned.version = VectorClock.new
70
+ @old_versioned.version.timestamp = (Time.now-86400).to_i * 1000
71
+
72
+ @new_versioned = Versioned.new
73
+ @new_versioned.value = "new value"
74
+ @new_versioned.version = VectorClock.new
75
+ @new_versioned.version.timestamp = (Time.now).to_i * 1000
76
+
77
+ @versions = []
78
+ @versions << @old_versioned
79
+ @versions << @new_versioned
80
+ end
81
+
82
+ it "should have a default resolver" do
83
+ @client.should respond_to(:conflict_resolver)
84
+ end
85
+
86
+ it "should pick a default version form a list of versions, and should be the most recent value" do
87
+ @client.resolve_conflicts(@versions).should eql(@new_versioned)
88
+ end
89
+
90
+ it "should allow a custom conflict resolver" do
91
+ @client = VoldemortClient.new("test", "localhost:6666") do |versions|
92
+ versions.first # just return the first version
93
+ end
94
+ @client.resolve_conflicts(@versions).should eql(@old_versioned)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe VoldemortNode do
4
+
5
+ before(:each) do
6
+ @voldemort_node = VoldemortNode.new
7
+ end
8
+
9
+ describe "default methods" do
10
+
11
+ it "should have id, host, port, http_port, admin_port and partitions" do
12
+ [:id, :host, :port, :http_port, :admin_port, :partitions].each do |m|
13
+ @voldemort_node.should respond_to(m)
14
+ end
15
+ end
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: voldemort-rb
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ version: "0.1"
9
+ platform: ruby
10
+ authors:
11
+ - Alejandro Crosa
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-03-05 00:00:00 -08:00
17
+ default_executable:
18
+ dependencies: []
19
+
20
+ description: voldemort-rb allows you to connect to the Voldemort descentralized key value store.
21
+ email:
22
+ - alejandrocrosa@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - CHANGELOG
31
+ - MIT-LICENSE
32
+ - README.md
33
+ - Rakefile
34
+ - lib/voldemort-rb.rb
35
+ - lib/connection/connection.rb
36
+ - lib/connection/tcp_connection.rb
37
+ - lib/connection/voldemort_node.rb
38
+ - lib/protos/voldemort-client.pb.rb
39
+ - lib/protos/voldemort-client.proto
40
+ - spec/connection_spec.rb
41
+ - spec/tcp_connection_spec.rb
42
+ - spec/voldemort_node_spec.rb
43
+ - spec/voldemort_client_spec.rb
44
+ - spec/spec_helper.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/acrosa/voldemort-rb
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.6
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: A Ruby client for the Voldemort distributed key value store
75
+ test_files: []
76
+