riak-client 2.3.2 → 2.4.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/{README.markdown → README.md} +72 -0
  3. data/Rakefile +9 -10
  4. data/lib/riak/bucket.rb +2 -2
  5. data/lib/riak/client.rb +51 -19
  6. data/lib/riak/client/beefcake/messages.rb +10 -10
  7. data/lib/riak/client/beefcake/object_methods.rb +5 -3
  8. data/lib/riak/client/beefcake/protocol.rb +30 -3
  9. data/lib/riak/client/beefcake/socket.rb +10 -13
  10. data/lib/riak/client/beefcake/time_series_get_operator.rb +8 -3
  11. data/lib/riak/client/beefcake/time_series_list_operator.rb +8 -3
  12. data/lib/riak/client/beefcake/time_series_query_operator.rb +8 -3
  13. data/lib/riak/client/beefcake/ts_cell_codec.rb +11 -1
  14. data/lib/riak/client/beefcake_protobuffs_backend.rb +23 -14
  15. data/lib/riak/client/protobuffs_backend.rb +1 -0
  16. data/lib/riak/counter.rb +2 -2
  17. data/lib/riak/rcontent.rb +30 -7
  18. data/lib/riak/robject.rb +9 -3
  19. data/lib/riak/time_series/list.rb +2 -3
  20. data/lib/riak/time_series/query.rb +2 -1
  21. data/lib/riak/time_series/read.rb +2 -1
  22. data/lib/riak/util/gzip.rb +44 -0
  23. data/lib/riak/version.rb +1 -1
  24. data/riak-client.gemspec +11 -11
  25. data/spec/integration/riak/encodings/kv_spec.rb +6 -0
  26. data/spec/integration/riak/protobuffs/timeouts_spec.rb +174 -0
  27. data/spec/integration/riak/time_series_spec.rb +8 -6
  28. data/spec/riak/beefcake_protobuffs_backend/object_methods_spec.rb +1 -0
  29. data/spec/riak/beefcake_protobuffs_backend/ts_cell_codec_spec.rb +9 -1
  30. data/spec/riak/client_spec.rb +49 -2
  31. data/spec/riak/robject_spec.rb +2 -1
  32. data/spec/riak/stamp_spec.rb +54 -54
  33. data/spec/riak/time_series/listing_spec.rb +1 -0
  34. data/spec/riak/util/gzip_spec.rb +37 -0
  35. data/spec/support/unified_backend_examples.rb +22 -0
  36. metadata +28 -23
@@ -2,11 +2,16 @@ require_relative './ts_cell_codec'
2
2
  require_relative './operator'
3
3
 
4
4
  class Riak::Client::BeefcakeProtobuffsBackend
5
- def time_series_list_operator
6
- TimeSeriesListOperator.new(self)
5
+ def time_series_list_operator(convert_timestamp)
6
+ TimeSeriesListOperator.new(self, convert_timestamp)
7
7
  end
8
8
 
9
9
  class TimeSeriesListOperator < Operator
10
+ def initialize(backend, convert_timestamp)
11
+ super(backend)
12
+ @convert_timestamp = convert_timestamp
13
+ end
14
+
10
15
  def list(table_name, block, options = { })
11
16
  request = TsListKeysReq.new options.merge(table: table_name)
12
17
 
@@ -25,7 +30,7 @@ class Riak::Client::BeefcakeProtobuffsBackend
25
30
  backend.protocol do |p|
26
31
  p.write :TsListKeysReq, request
27
32
 
28
- codec = TsCellCodec.new
33
+ codec = TsCellCodec.new(@convert_timestamp)
29
34
 
30
35
  while resp = p.expect(:TsListKeysResp, TsListKeysResp)
31
36
  break if resp.done
@@ -2,11 +2,16 @@ require_relative './ts_cell_codec'
2
2
  require_relative './operator'
3
3
 
4
4
  class Riak::Client::BeefcakeProtobuffsBackend
5
- def time_series_query_operator
6
- TimeSeriesQueryOperator.new(self)
5
+ def time_series_query_operator(convert_timestamp)
6
+ TimeSeriesQueryOperator.new(self, convert_timestamp)
7
7
  end
8
8
 
9
9
  class TimeSeriesQueryOperator < Operator
10
+ def initialize(backend, convert_timestamp)
11
+ super(backend)
12
+ @convert_timestamp = convert_timestamp
13
+ end
14
+
10
15
  def query(base, interpolations = { })
11
16
  interpolator = TsInterpolation.new base: base
12
17
  interpolator.interpolations = pairs_for interpolations
@@ -20,7 +25,7 @@ class Riak::Client::BeefcakeProtobuffsBackend
20
25
 
21
26
  return [] if :empty == result
22
27
 
23
- codec = TsCellCodec.new
28
+ codec = TsCellCodec.new(@convert_timestamp)
24
29
 
25
30
  collection = Riak::TimeSeries::Collection.
26
31
  new(result.rows.map do |row|
@@ -2,6 +2,12 @@ require 'bigdecimal'
2
2
 
3
3
  class Riak::Client::BeefcakeProtobuffsBackend
4
4
  class TsCellCodec
5
+ attr_accessor :convert_timestamp
6
+
7
+ def initialize(convert_timestamp = false)
8
+ @convert_timestamp = convert_timestamp
9
+ end
10
+
5
11
  def cells_for(measures)
6
12
  measures.map{ |m| cell_for m }
7
13
  end
@@ -57,7 +63,11 @@ class Riak::Client::BeefcakeProtobuffsBackend
57
63
 
58
64
  def timestamp(cell)
59
65
  return false unless cell.timestamp_value.is_a? Integer
60
- Time.at(cell.timestamp_value.to_f / 1000)
66
+ return cell.timestamp_value unless @convert_timestamp
67
+ tsv = cell.timestamp_value
68
+ secs = tsv / 1000
69
+ msec = tsv % 1000
70
+ Time.at(secs, msec * 1000)
61
71
  end
62
72
  end
63
73
  end
@@ -31,7 +31,11 @@ module Riak
31
31
  end
32
32
 
33
33
  def protocol
34
- p = Protocol.new socket
34
+ p = Protocol.new(
35
+ socket,
36
+ read_timeout: client.read_timeout,
37
+ write_timeout: client.write_timeout
38
+ )
35
39
  in_request = false
36
40
  result = nil
37
41
  begin
@@ -45,7 +49,12 @@ module Riak
45
49
  end
46
50
 
47
51
  def new_socket
48
- BeefcakeSocket.new @node.host, @node.pb_port, authentication: client.authentication
52
+ BeefcakeSocket.new(
53
+ @node.host,
54
+ @node.pb_port,
55
+ authentication: client.authentication,
56
+ connect_timeout: client.connect_timeout
57
+ )
49
58
  end
50
59
 
51
60
  def ping
@@ -333,18 +342,18 @@ module Riak
333
342
  def get_index(bucket, index, query, query_options = {}, &block)
334
343
  return super unless pb_indexes?
335
344
  bucket = bucket.name if Bucket === bucket
336
- if Range === query
337
- options = {
338
- :qtype => RpbIndexReq::IndexQueryType::RANGE,
339
- :range_min => query.begin.to_s,
340
- :range_max => query.end.to_s
341
- }
342
- else
343
- options = {
344
- :qtype => RpbIndexReq::IndexQueryType::EQ,
345
- :key => query.to_s
346
- }
347
- end
345
+ options = if Range === query
346
+ {
347
+ :qtype => RpbIndexReq::IndexQueryType::RANGE,
348
+ :range_min => query.begin.to_s,
349
+ :range_max => query.end.to_s
350
+ }
351
+ else
352
+ {
353
+ :qtype => RpbIndexReq::IndexQueryType::EQ,
354
+ :key => query.to_s
355
+ }
356
+ end
348
357
 
349
358
  options.merge!(:bucket => bucket, :index => index.to_s)
350
359
  options.merge!(query_options)
@@ -32,6 +32,7 @@ module Riak
32
32
 
33
33
  attr_accessor :client
34
34
  attr_accessor :node
35
+
35
36
  def initialize(client, node)
36
37
  @client = client
37
38
  @node = node
@@ -14,8 +14,8 @@ module Riak
14
14
  # @param [Bucket] bucket the {Riak::Bucket} for this counter
15
15
  # @param [String] key the name of the counter
16
16
  def initialize(bucket, key)
17
- raise ArgumentError, t("bucket_type", bucket: bucket.inspect) unless bucket.is_a? Bucket
18
- raise ArgumentError, t("string_type", string: key.inspect) unless key.is_a? String
17
+ raise ArgumentError, t('bucket_type', bucket: bucket.inspect) unless bucket.is_a? Bucket
18
+ raise ArgumentError, t('string_type', string: key.inspect) unless key.is_a? String
19
19
  @bucket, @key = bucket, key
20
20
  @client = bucket.client
21
21
 
@@ -2,6 +2,7 @@ require 'set'
2
2
  require 'time'
3
3
  require 'yaml'
4
4
  require 'forwardable'
5
+ require 'riak/util/gzip'
5
6
  require 'riak/util/translation'
6
7
  require 'riak/serializers'
7
8
 
@@ -17,6 +18,9 @@ module Riak
17
18
  # @return [String] the MIME content type of the value
18
19
  attr_accessor :content_type
19
20
 
21
+ # @return [String] the content encoding of the object, e.g. "gzip"
22
+ attr_accessor :content_encoding
23
+
20
24
  # @return [Set<Link>] a Set of {Riak::Link} objects for relationships between this object and other resources
21
25
  attr_accessor :links
22
26
 
@@ -63,7 +67,7 @@ module Riak
63
67
  def data
64
68
  if @raw_data && !@data
65
69
  raw = @raw_data.respond_to?(:read) ? @raw_data.read : @raw_data
66
- @data = deserialize(raw)
70
+ @data = deserialize(decompress(raw))
67
71
  @raw_data = nil
68
72
  end
69
73
  @data
@@ -86,7 +90,7 @@ module Riak
86
90
  # @return [String] raw data stored in riak for this object's key
87
91
  def raw_data
88
92
  if @data && !@raw_data
89
- @raw_data = serialize(@data)
93
+ @raw_data = compress(serialize(@data))
90
94
  @data = nil
91
95
  end
92
96
  @raw_data
@@ -125,6 +129,26 @@ module Riak
125
129
  Serializers.deserialize(@content_type, body)
126
130
  end
127
131
 
132
+ # Compresses the given string using gzip if {#content_encoding} is set to "gzip".
133
+ # Otherwise the given string is returned as-is.
134
+ # This method is called internally when storing the object.
135
+ # @param [String] data
136
+ # @return [String]
137
+ def compress(data)
138
+ return data unless content_encoding == "gzip"
139
+ Util::Gzip.compress(data)
140
+ end
141
+
142
+ # Decompresses the given string using gzip if {#content_encoding} is set to "gzip".
143
+ # Otherwise the given string is returned as-is.
144
+ # This method is called internally when loading the object.
145
+ # @param [String] data
146
+ # @return [String]
147
+ def decompress(data)
148
+ return data unless content_encoding == "gzip"
149
+ Util::Gzip.decompress(data)
150
+ end
151
+
128
152
  # @return [String] A representation suitable for IRB and debugging output
129
153
  def inspect
130
154
  body = if @data || Serializers[content_type]
@@ -159,11 +183,10 @@ module Riak
159
183
 
160
184
  private
161
185
  def extract_if_present(hash, key, attribute = nil)
162
- if hash[key].present?
163
- attribute ||= key
164
- value = block_given? ? yield(hash[key]) : hash[key]
165
- send("#{attribute}=", value)
166
- end
186
+ return unless hash[key].present?
187
+ attribute ||= key
188
+ value = block_given? ? yield(hash[key]) : hash[key]
189
+ send("#{attribute}=", value)
167
190
  end
168
191
 
169
192
  def new_index_hash
@@ -56,6 +56,7 @@ module Riak
56
56
  end
57
57
 
58
58
  def_delegators :content, :content_type, :content_type=,
59
+ :content_encoding, :content_encoding=,
59
60
  :links, :links=,
60
61
  :etag, :etag=,
61
62
  :last_modified, :last_modified=,
@@ -63,7 +64,8 @@ module Riak
63
64
  :indexes, :indexes=,
64
65
  :data, :data=,
65
66
  :raw_data, :raw_data=,
66
- :deserialize, :serialize
67
+ :deserialize, :serialize,
68
+ :decompress, :compress
67
69
 
68
70
  # Attempts to resolve conflict using the registered conflict callbacks.
69
71
  #
@@ -139,8 +141,12 @@ module Riak
139
141
  # @raise [Conflict] if the object has siblings
140
142
  def store(options = {})
141
143
  fail Conflict, self if conflict?
142
- fail ArgumentError, t('content_type_undefined') unless content_type.present?
143
- fail ArgumentError, t('zero_length_key') if key == ''
144
+ raise ArgumentError, t('content_type_undefined') unless content_type.present?
145
+ raise ArgumentError, t('zero_length_key') if key == ''
146
+ # NB: key can be nil to indicate that Riak should generate one
147
+ unless key.nil? || key.is_a?(String)
148
+ raise ArgumentError, t('string_type', :string => key)
149
+ end
144
150
  @bucket.client.store_object(self, default(options))
145
151
  self
146
152
  end
@@ -44,9 +44,8 @@ module Riak::TimeSeries
44
44
  potential_results = nil
45
45
 
46
46
  client.backend do |be|
47
- potential_results = be.time_series_list_operator.list(table_name,
48
- block,
49
- options)
47
+ op = be.time_series_list_operator(client.convert_timestamp)
48
+ potential_results = op.list(table_name, block, options)
50
49
  end
51
50
 
52
51
  return @results = potential_results unless block_given?
@@ -36,7 +36,8 @@ module Riak::TimeSeries
36
36
  # attribute
37
37
  def issue!
38
38
  @results = client.backend do |be|
39
- be.time_series_query_operator.query(query_text, interpolations)
39
+ op = be.time_series_query_operator(client.convert_timestamp)
40
+ op.query(query_text, interpolations)
40
41
  end
41
42
  end
42
43
  end
@@ -11,7 +11,8 @@ module Riak::TimeSeries
11
11
 
12
12
  def read!
13
13
  client.backend do |be|
14
- be.time_series_get_operator.get(table_name, key)
14
+ op = be.time_series_get_operator(client.convert_timestamp)
15
+ op.get(table_name, key)
15
16
  end
16
17
  end
17
18
  end
@@ -0,0 +1,44 @@
1
+ require 'zlib'
2
+ require 'stringio'
3
+
4
+ module Riak
5
+ module Util
6
+ # Borrowed from ActiveSupport
7
+ # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/gzip.rb
8
+ #
9
+ # A convenient wrapper for the zlib standard library that allows
10
+ # compression/decompression of strings with gzip.
11
+ #
12
+ # gzip = Riak::Util::Gzip.compress('compress me!')
13
+ # # => "\x1F\x8B\b\x00o\x8D\xCDO\x00\x03K\xCE\xCF-(J-.V\xC8MU\x04\x00R>n\x83\f\x00\x00\x00"
14
+ #
15
+ # Riak::Util::Gzip.decompress(gzip)
16
+ # # => "compress me!"
17
+ module Gzip
18
+ class Stream < StringIO
19
+ def initialize(*)
20
+ super
21
+ set_encoding "BINARY"
22
+ end
23
+
24
+ def close
25
+ rewind
26
+ end
27
+ end
28
+
29
+ # Decompresses a gzipped string.
30
+ def self.decompress(source)
31
+ Zlib::GzipReader.new(StringIO.new(source)).read
32
+ end
33
+
34
+ # Compresses a string using gzip.
35
+ def self.compress(source, level = Zlib::DEFAULT_COMPRESSION, strategy = Zlib::DEFAULT_STRATEGY)
36
+ output = Stream.new
37
+ gz = Zlib::GzipWriter.new(output, level, strategy)
38
+ gz.write(source)
39
+ gz.close
40
+ output.string
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,3 +1,3 @@
1
1
  module Riak
2
- VERSION = "2.3.2"
2
+ VERSION = "2.4.0.pre1"
3
3
  end
@@ -10,23 +10,23 @@ Gem::Specification.new do |gem|
10
10
  gem.email = ['bryce@basho.com']
11
11
  gem.homepage = "http://github.com/basho/riak-ruby-client"
12
12
  gem.authors = ['Bryce Kerley']
13
- gem.license = 'Apache 2.0'
13
+ gem.license = 'Apache-2.0'
14
14
 
15
15
  gem.required_ruby_version = '>= 1.9.3'
16
16
 
17
17
  # Deps
18
- gem.add_development_dependency "rspec", "~> 3.0"
19
- gem.add_development_dependency 'rake', '~> 10.1.1'
20
- gem.add_development_dependency 'yard', '~> 0.8.7'
18
+ gem.add_development_dependency 'rspec', '~> 3.0'
19
+ gem.add_development_dependency 'rake', '~> 10.1'
20
+ gem.add_development_dependency 'yard', '~> 0.8'
21
21
  gem.add_development_dependency 'kramdown', '~> 1.4'
22
- gem.add_development_dependency 'simplecov', '~> 0.10.0'
23
- gem.add_development_dependency "instrumentable", "~> 1.1.0"
24
- gem.add_development_dependency "rubocop", "~> 0.28.0"
22
+ gem.add_development_dependency 'simplecov', '~> 0.10'
23
+ gem.add_development_dependency 'instrumentable', '~> 1.1'
24
+ gem.add_development_dependency 'rubocop', '~> 0.40'
25
25
 
26
- gem.add_runtime_dependency "i18n", ">=0.6.8"
27
- gem.add_runtime_dependency "beefcake", "~> 1.1"
28
- gem.add_runtime_dependency "multi_json", "~>1.0"
29
- gem.add_runtime_dependency "innertube", "~>1.0.2"
26
+ gem.add_runtime_dependency 'i18n', '~> 0.6'
27
+ gem.add_runtime_dependency 'beefcake', '~> 1.1'
28
+ gem.add_runtime_dependency 'multi_json', '~> 1.0'
29
+ gem.add_runtime_dependency 'innertube', '~> 1.0'
30
30
  gem.add_runtime_dependency 'cert_validator', '~> 0.0.1'
31
31
 
32
32
  # Files
@@ -77,5 +77,11 @@ describe 'Encoding and Riak KV', integration: true, test_client: true do
77
77
 
78
78
  expect(binary_bucket.keys).to include random_binary_string
79
79
  end
80
+
81
+ it 'throws an exception when non-string used as key' do
82
+ expect do
83
+ binary_bucket.get(1234)
84
+ end.to raise_error ArgumentError, /is not a String/
85
+ end
80
86
  end
81
87
  end
@@ -0,0 +1,174 @@
1
+ require 'socket'
2
+ require 'spec_helper'
3
+
4
+ require 'riak/client/beefcake/messages'
5
+ require 'riak/client/beefcake/protocol'
6
+
7
+ describe 'Protocol Buffers', test_client: true, integration: true do
8
+ describe 'timeouts' do
9
+ it 'raises error on connect timeout' do
10
+ # unroutable TEST-NET (https://tools.ietf.org/html/rfc5737)
11
+ config = {}
12
+ config[:host] = '192.0.2.0'
13
+ config[:pb_port] = 65535
14
+
15
+ config[:connect_timeout] = 0.0001
16
+ client = Riak::Client.new(config)
17
+
18
+ expect do
19
+ client.ping
20
+ end.to raise_error RuntimeError, /timed out/
21
+ end
22
+
23
+ it 'raises error on read timeout' do
24
+ ok_to_continue = false
25
+ quitting = false
26
+ port = 0
27
+
28
+ server = nil
29
+ thr = Thread.new do
30
+ server = TCPServer.new port
31
+ port = server.addr[1]
32
+ ok_to_continue = true
33
+ loop do
34
+ begin
35
+ Thread.start(server.accept) do |s|
36
+ loop do
37
+ p = Riak::Client::BeefcakeProtobuffsBackend::Protocol.new s
38
+ begin
39
+ msgname, _body = p.receive
40
+ rescue IOError
41
+ break if quitting
42
+ raise
43
+ end
44
+ case msgname
45
+ when :PingReq
46
+ sleep 0.5
47
+ p.write :PingResp
48
+ else
49
+ $stderr.puts("unknown msgname: #{msgname}")
50
+ end
51
+ end
52
+ end
53
+ rescue IOError
54
+ break if quitting
55
+ raise
56
+ end
57
+ end
58
+ end
59
+
60
+ loop do
61
+ break if ok_to_continue
62
+ sleep 0.1
63
+ end
64
+ ok_to_continue = false
65
+
66
+ config = {}
67
+ config[:pb_port] = port
68
+ config[:client_id] = port
69
+ config[:read_timeout] = 0.0001
70
+ client = Riak::Client.new(config)
71
+
72
+ max_ping_attempts = 16
73
+ ping_count = 0
74
+ loop do
75
+ begin
76
+ client.ping
77
+ ping_count += 1
78
+ break if ping_count > max_ping_attempts
79
+ rescue RuntimeError => e
80
+ break if e.message =~ /timed out/
81
+ end
82
+ sleep 0.5
83
+ end
84
+
85
+ quitting = true
86
+ server.close
87
+ thr.join
88
+
89
+ expect(ping_count).to be < max_ping_attempts
90
+ end
91
+
92
+ it 'raises error on write timeout' do
93
+ ok_to_continue = false
94
+ quitting = false
95
+ port = 0
96
+
97
+ server = nil
98
+ thr = Thread.new do
99
+ server = TCPServer.new port
100
+ port = server.addr[1]
101
+ ok_to_continue = true
102
+ loop do
103
+ begin
104
+ Thread.start(server.accept) do |s|
105
+ loop do
106
+ p = Riak::Client::BeefcakeProtobuffsBackend::Protocol.new s
107
+ begin
108
+ msgname, _body = p.receive
109
+ rescue IOError
110
+ break if quitting
111
+ raise
112
+ end
113
+ case msgname
114
+ when :PingReq
115
+ p.write :PingResp
116
+ when :GetServerInfoReq
117
+ r = Riak::Client::BeefcakeProtobuffsBackend::RpbGetServerInfoResp.new
118
+ r.node = 'dev1@127.0.0.1'.force_encoding('BINARY')
119
+ r.server_version = '2.1.4'.force_encoding('BINARY')
120
+ p.write :GetServerInfoResp, r
121
+ when :PutReq
122
+ r = Riak::Client::BeefcakeProtobuffsBackend::RpbPutResp.new
123
+ p.write :PutResp, r
124
+ else
125
+ $stderr.puts("unknown msgname: #{msgname}")
126
+ end
127
+ end
128
+ end
129
+ rescue IOError
130
+ break if quitting
131
+ raise
132
+ end
133
+ end
134
+ end
135
+
136
+ loop do
137
+ break if ok_to_continue
138
+ sleep 0.1
139
+ end
140
+ ok_to_continue = false
141
+
142
+ config = {}
143
+ config[:pb_port] = port
144
+ config[:client_id] = port
145
+ config[:write_timeout] = 0.0001
146
+ client = Riak::Client.new(config)
147
+
148
+ bucket = client.bucket('timeouts')
149
+
150
+ max_store_attempts = 16
151
+ store_count = 0
152
+ loop do
153
+ begin
154
+ obj = bucket.new "obj-#{store_count}"
155
+ # write enough data to grow beyond socket buffer capacity
156
+ obj.data = SecureRandom.urlsafe_base64(10_000_000)
157
+ obj.content_type = 'text/plain'
158
+ obj.store
159
+ store_count += 1
160
+ break if store_count > max_store_attempts
161
+ rescue RuntimeError => e
162
+ break if e.message =~ /timed out/
163
+ end
164
+ sleep 0.5
165
+ end
166
+
167
+ quitting = true
168
+ server.close
169
+ thr.join
170
+
171
+ expect(store_count).to be < max_store_attempts
172
+ end
173
+ end
174
+ end