poseidon 0.0.5.pre1 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -3
  3. data/CHANGES.md +11 -3
  4. data/README.md +4 -1
  5. data/Rakefile +1 -1
  6. data/lib/poseidon.rb +1 -0
  7. data/lib/poseidon/broker_pool.rb +15 -5
  8. data/lib/poseidon/compression/gzip_codec.rb +3 -3
  9. data/lib/poseidon/compression/snappy_codec.rb +14 -2
  10. data/lib/poseidon/connection.rb +9 -0
  11. data/lib/poseidon/partition_consumer.rb +5 -5
  12. data/lib/poseidon/producer.rb +9 -6
  13. data/lib/poseidon/producer_compression_config.rb +5 -4
  14. data/lib/poseidon/protocol/request_buffer.rb +5 -14
  15. data/lib/poseidon/protocol/response_buffer.rb +7 -7
  16. data/lib/poseidon/sync_producer.rb +5 -3
  17. data/lib/poseidon/version.rb +1 -1
  18. data/poseidon.gemspec +2 -1
  19. data/spec/integration/multiple_brokers/consumer_spec.rb +1 -1
  20. data/spec/integration/multiple_brokers/metadata_failures_spec.rb +1 -1
  21. data/spec/integration/multiple_brokers/rebalance_spec.rb +1 -1
  22. data/spec/integration/multiple_brokers/round_robin_spec.rb +1 -1
  23. data/spec/integration/simple/compression_spec.rb +2 -2
  24. data/spec/integration/simple/connection_spec.rb +4 -4
  25. data/spec/integration/simple/multiple_brokers_spec.rb +1 -1
  26. data/spec/integration/simple/simple_producer_and_consumer_spec.rb +7 -7
  27. data/spec/integration/simple/truncated_messages_spec.rb +4 -4
  28. data/spec/integration/simple/unavailable_broker_spec.rb +1 -1
  29. data/spec/spec_helper.rb +1 -7
  30. data/spec/unit/broker_pool_spec.rb +14 -14
  31. data/spec/unit/cluster_metadata_spec.rb +1 -1
  32. data/spec/unit/compression/gzip_codec_spec.rb +34 -0
  33. data/spec/unit/compression/snappy_codec_spec.rb +49 -0
  34. data/spec/unit/compression_spec.rb +1 -1
  35. data/spec/unit/connection_spec.rb +1 -1
  36. data/spec/unit/fetched_message_spec.rb +1 -1
  37. data/spec/unit/message_conductor_spec.rb +1 -1
  38. data/spec/unit/message_set_spec.rb +1 -1
  39. data/spec/unit/message_spec.rb +2 -2
  40. data/spec/unit/message_to_send_spec.rb +1 -1
  41. data/spec/unit/messages_for_broker_spec.rb +3 -3
  42. data/spec/unit/messages_to_send_batch_spec.rb +4 -4
  43. data/spec/unit/messages_to_send_spec.rb +8 -8
  44. data/spec/unit/partition_consumer_spec.rb +13 -13
  45. data/spec/unit/producer_compression_config_spec.rb +8 -1
  46. data/spec/unit/producer_spec.rb +10 -4
  47. data/spec/unit/protocol/request_buffer_spec.rb +16 -0
  48. data/spec/unit/protocol_spec.rb +5 -5
  49. data/spec/unit/sync_producer_spec.rb +22 -22
  50. data/spec/unit/topic_metadata_spec.rb +1 -1
  51. metadata +28 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6b96a319eb67baf9aa7081d2c2800cf9d24eb672
4
- data.tar.gz: fb213c7eb1e31de507c3160bdd6fc6a4fd04084c
3
+ metadata.gz: a9abab19842081422bfcb903cf47a9dd248202bf
4
+ data.tar.gz: 94a4d93957d331fd9b1c78e8f32f0eab323135ba
5
5
  SHA512:
6
- metadata.gz: 1f6daff2a688fafccd154005ef4a3c99c1cb0b3b4cadaf3776695b49607b25f4e2b58863341aa3a15bc47fdcfa40cfa16de0860c3c75d68a28b237185c94c6e7
7
- data.tar.gz: 0861e6e8539a62174f01ea7bf9cd3d8c6bec9b77b38488465484e08a670d38713bd8c61636b6496818052f85cf9003eb2ec40e11ae553d28d8ae5b2b22bcb69f
6
+ metadata.gz: a1590c28e36139583f9fdd6558e7cc2b3497806909c2811fd68184cc5c8b5d3bdfca1c87746901d68cf7531b8d0641490dcb4871e1b583afdf30b8e2dc6084fb
7
+ data.tar.gz: 1b3f297c8c75ebdcc5bd2560eb56d6b2f8e390a4ffaf6ee3277ace90e8b10b3c77f9abc929e658dddbd60d5dcf2a94f91a9ae418f2e2f84ad02bc2f220d41653
@@ -2,13 +2,13 @@ laguage: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
- - 2.1.2
5
+ - 2.1
6
6
  - ruby-head
7
7
  - jruby-19mode
8
8
  - jruby-head
9
- - rbx-19mode
9
+ - rbx-2
10
10
  matrix:
11
11
  allow_failures:
12
12
  - rvm: ruby-head
13
13
  - rvm: jruby-head
14
- - rvm: rbx-19mode
14
+ - rvm: rbx-2
data/CHANGES.md CHANGED
@@ -1,6 +1,14 @@
1
- # 0.0.5 (Unreleased)
2
-
3
- * Fix serious bug where we would send messages to the wrong partition [GH-36]. (Thanks @sclasen and @jorgeortiz85 for tracking this down.)
1
+ # 0.0.5
2
+
3
+ * Add support for negative offsets. [GH-24]
4
+ * Fix serious bug where we would send messages to the wrong partition. [GH-36] (Thanks @sclasen and @jorgeortiz85 for tracking this down.)
5
+ * Better error message when we can't connect to a broker. [GH-42]
6
+ * Handle broker rebalances. [GH-43]
7
+ * PartitionConsumer: Block for messages by default. [GH-48]
8
+ * Add a logger to help debug issues. [GH-51]
9
+ * Add snappy support. [GH-57]
10
+ * Allow `:none` value for `:compression_codec` option. [GH-72]
11
+ * Allow request buffer to accept mixed encodings. [GH-74]
4
12
 
5
13
  # 0.0.4
6
14
 
data/README.md CHANGED
@@ -30,7 +30,6 @@ producer.send_messages(messages)
30
30
 
31
31
  More detailed [Poseidon::Producer](http://rubydoc.info/github/bpot/poseidon/Poseidon/Producer) documentation.
32
32
 
33
-
34
33
  ### Fetching messages from Kafka
35
34
 
36
35
  ```ruby
@@ -49,6 +48,10 @@ end
49
48
 
50
49
  More detailed [Poseidon::PartitionConsumer](http://rubydoc.info/github/bpot/poseidon/Poseidon/PartitionConsumer) documentation.
51
50
 
51
+ ### Using snappy compression
52
+
53
+ To use snappy compression in your producers or consumers, install the [snappy](http://rubygems.org/gems/snappy) gem or simply add `gem 'snappy'` to your project's Gemfile.
54
+
52
55
  ## Semantic Versioning
53
56
 
54
57
  This gem follows [SemVer](http://semver.org). In particular, the public API should not be considered stable and anything may change without warning until Version 1.0.0. Additionally, for the purposes of the versioning the public API is everything documented in the [public API docs](http://rubydoc.info/github/bpot/poseidon).
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new("spec:unit") do |t|
5
- t.pattern = 'spec/unit/*_spec.rb'
5
+ t.pattern = 'spec/unit/**/*_spec.rb'
6
6
  end
7
7
 
8
8
  RSpec::Core::RakeTask.new('spec:integration:simple') do |t|
@@ -4,6 +4,7 @@ require 'zlib'
4
4
  require 'thread'
5
5
  require 'set'
6
6
  require 'logger'
7
+ require 'stringio'
7
8
 
8
9
  # Top level Poseidon namespace
9
10
  #
@@ -5,6 +5,15 @@ module Poseidon
5
5
  class BrokerPool
6
6
  class UnknownBroker < StandardError; end
7
7
 
8
+ # @yieldparam [BrokerPool]
9
+ def self.open(client_id, seed_brokers, socket_timeout_ms, &block)
10
+ broker_pool = new(client_id, seed_brokers, socket_timeout_ms)
11
+
12
+ yield broker_pool
13
+ ensure
14
+ broker_pool.close
15
+ end
16
+
8
17
  # @param [String] client_id
9
18
  def initialize(client_id, seed_brokers, socket_timeout_ms)
10
19
  @connections = {}
@@ -45,20 +54,21 @@ module Poseidon
45
54
  end
46
55
 
47
56
  # Closes all open connections to brokers
48
- def shutdown
57
+ def close
49
58
  @brokers.values(&:close)
50
59
  @brokers = {}
51
60
  end
52
61
 
62
+ alias_method :shutdown, :close
63
+
53
64
  private
54
65
  def fetch_metadata_from_broker(broker, topics)
55
66
  host, port = broker.split(":")
56
- c = Connection.new(host, port, @client_id, @socket_timeout_ms)
57
- c.topic_metadata(topics)
67
+ Connection.open(host, port, @client_id, @socket_timeout_ms) do |connection|
68
+ connection.topic_metadata(topics)
69
+ end
58
70
  rescue Connection::ConnectionFailedError
59
71
  return nil
60
- ensure
61
- c && c.close
62
72
  end
63
73
 
64
74
  def connection(broker_id)
@@ -7,8 +7,8 @@ module Poseidon
7
7
 
8
8
  def self.compress(s)
9
9
  io = StringIO.new
10
- io.set_encoding("ASCII-8BIT")
11
- gz = Zlib::GzipWriter.new io, Zlib::DEFAULT_COMPRESSION, Zlib::DEFAULT_STRATEGY, :encoding => "ASCII-8BIT"
10
+ io.set_encoding(Encoding::BINARY)
11
+ gz = Zlib::GzipWriter.new io, Zlib::DEFAULT_COMPRESSION, Zlib::DEFAULT_STRATEGY
12
12
  gz.write s
13
13
  gz.close
14
14
  io.string
@@ -16,7 +16,7 @@ module Poseidon
16
16
 
17
17
  def self.decompress(s)
18
18
  io = StringIO.new(s)
19
- Zlib::GzipReader.new(io, :encoding => "ASCII-8BIT").read
19
+ Zlib::GzipReader.new(io).read
20
20
  end
21
21
  end
22
22
  end
@@ -6,12 +6,24 @@ module Poseidon
6
6
  end
7
7
 
8
8
  def self.compress(s)
9
- raise "Unimplemented"
9
+ check!
10
+ Snappy.deflate(s)
10
11
  end
11
12
 
12
13
  def self.decompress(s)
13
- raise "Unimplemented"
14
+ check!
15
+ Snappy::Reader.new(StringIO.new(s)).read
14
16
  end
17
+
18
+ def self.check!
19
+ @checked ||= begin
20
+ require 'snappy'
21
+ true
22
+ rescue LoadError
23
+ raise "Snappy compression is not available, please install the 'snappy' gem"
24
+ end
25
+ end
26
+
15
27
  end
16
28
  end
17
29
  end
@@ -11,6 +11,15 @@ module Poseidon
11
11
  API_VERSION = 0
12
12
  REPLICA_ID = -1 # Replica id is always -1 for non-brokers
13
13
 
14
+ # @yieldparam [Connection]
15
+ def self.open(host, port, client_id, socket_timeout_ms, &block)
16
+ connection = new(host, port, client_id, socket_timeout_ms)
17
+
18
+ yield connection
19
+ ensure
20
+ connection.close
21
+ end
22
+
14
23
  attr_reader :host, :port
15
24
 
16
25
  # Create a new connection
@@ -22,13 +22,13 @@ module Poseidon
22
22
  # this is a stop-gap.
23
23
  #
24
24
  def self.consumer_for_partition(client_id, seed_brokers, topic, partition, offset, options = {})
25
- broker_pool = BrokerPool.new(client_id, seed_brokers, options[:socket_timeout_ms] || 10_000)
26
25
 
27
- cluster_metadata = ClusterMetadata.new
28
- cluster_metadata.update(broker_pool.fetch_metadata([topic]))
26
+ broker = BrokerPool.open(client_id, seed_brokers, options[:socket_timeout_ms] || 10_000) do |broker_pool|
27
+ cluster_metadata = ClusterMetadata.new
28
+ cluster_metadata.update(broker_pool.fetch_metadata([topic]))
29
29
 
30
- broker = cluster_metadata.lead_broker_for_partition(topic, partition)
31
- broker_pool.shutdown
30
+ cluster_metadata.lead_broker_for_partition(topic, partition)
31
+ end
32
32
 
33
33
  new(client_id, broker.host, broker.port, topic, partition, offset, options)
34
34
  end
@@ -72,15 +72,16 @@ module Poseidon
72
72
  class Producer
73
73
  # @api private
74
74
  VALID_OPTIONS = [
75
- :type,
76
- :compression_codec,
75
+ :ack_timeout_ms,
77
76
  :compressed_topics,
77
+ :compression_codec,
78
+ :max_send_retries,
78
79
  :metadata_refresh_interval_ms,
79
80
  :partitioner,
80
- :max_send_retries,
81
81
  :retry_backoff_ms,
82
82
  :required_acks,
83
- :ack_timeout_ms
83
+ :socket_timeout_ms,
84
+ :type,
84
85
  ]
85
86
 
86
87
  # @api private
@@ -163,11 +164,13 @@ module Poseidon
163
164
  end
164
165
 
165
166
  # Closes all open connections to brokers
166
- def shutdown
167
+ def close
167
168
  @shutdown = true
168
- @producer.shutdown
169
+ @producer.close
169
170
  end
170
171
 
172
+ alias_method :shutdown, :close
173
+
171
174
  private
172
175
  def validate_options(options)
173
176
  unknown_keys = options.keys - VALID_OPTIONS
@@ -3,15 +3,16 @@ module Poseidon
3
3
  class ProducerCompressionConfig
4
4
  COMPRESSION_CODEC_MAP = {
5
5
  :gzip => Compression::GzipCodec,
6
- :snappy => Compression::SnappyCodec
6
+ :snappy => Compression::SnappyCodec,
7
+ :none => nil
7
8
  }
8
9
 
9
10
  def initialize(compression_codec, compressed_topics)
10
11
  if compression_codec
11
- @compression_codec = COMPRESSION_CODEC_MAP[compression_codec]
12
- if @compression_codec.nil?
13
- raise ArgumentError, "Uknown compression codec: '#{compression_codec}' (accepted: #{COMPRESSION_CODEC_MAP.keys.inspect})"
12
+ unless COMPRESSION_CODEC_MAP.has_key?(compression_codec)
13
+ raise ArgumentError, "Unknown compression codec: '#{compression_codec}' (accepted: #{COMPRESSION_CODEC_MAP.keys.inspect})"
14
14
  end
15
+ @compression_codec = COMPRESSION_CODEC_MAP[compression_codec]
15
16
  else
16
17
  @compression_codec = nil
17
18
  end
@@ -4,13 +4,14 @@ module Poseidon
4
4
  #
5
5
  # API parallels the primitive types described on the wiki, with some
6
6
  # sugar for prepending message sizes and checksums.
7
- # (https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol#AGuideToTheKafkaProtocol-ProtocolPrimitiveTypes)
7
+ # (https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol#AGuideToTheKafkaProtocol-ProtocolPrimitiveTypes)
8
8
  class RequestBuffer
9
9
  def initialize
10
- @s = ''
10
+ @s = ''.encode(Encoding::BINARY)
11
11
  end
12
12
 
13
13
  def append(string)
14
+ string = string.dup.force_encoding(Encoding::BINARY)
14
15
  @s << string
15
16
  nil
16
17
  end
@@ -32,7 +33,7 @@ module Poseidon
32
33
  end
33
34
 
34
35
  # Add a string
35
- #
36
+ #
36
37
  # @param [String] string
37
38
  def string(string)
38
39
  if string.nil?
@@ -53,33 +54,23 @@ module Poseidon
53
54
  end
54
55
 
55
56
  def prepend_crc32
56
- ensure_ascii
57
57
  checksum_pos = @s.bytesize
58
58
  @s += " "
59
59
  yield
60
- ensure_ascii
61
60
  @s[checksum_pos] = [Zlib::crc32(@s[(checksum_pos+1)..-1])].pack("N")
62
61
  nil
63
62
  end
64
63
 
65
64
  def prepend_size
66
- ensure_ascii
67
65
  size_pos = @s.bytesize
68
66
  @s += " "
69
67
  yield
70
- ensure_ascii
71
68
  @s[size_pos] = [(@s.bytesize-1) - size_pos].pack("N")
72
69
  nil
73
70
  end
74
71
 
75
72
  def to_s
76
- ensure_ascii
77
- end
78
-
79
- private
80
-
81
- def ensure_ascii
82
- @s.force_encoding("ASCII-8BIT")
73
+ @s
83
74
  end
84
75
  end
85
76
  end
@@ -7,44 +7,44 @@ module Poseidon
7
7
  end
8
8
 
9
9
  def int8
10
- byte = @s.slice(@pos, 1).unpack("C").first
10
+ byte = @s.byteslice(@pos, 1).unpack("C").first
11
11
  @pos += 1
12
12
  byte
13
13
  end
14
14
 
15
15
  def int16
16
- short = @s.slice(@pos, 2).unpack("s>").first
16
+ short = @s.byteslice(@pos, 2).unpack("s>").first
17
17
  @pos += 2
18
18
  short
19
19
  end
20
20
 
21
21
  def int32
22
- int = @s.slice(@pos, 4).unpack("l>").first
22
+ int = @s.byteslice(@pos, 4).unpack("l>").first
23
23
  @pos += 4
24
24
  int
25
25
  end
26
26
 
27
27
  def int64
28
- long = @s.slice(@pos, 8).unpack("q>").first
28
+ long = @s.byteslice(@pos, 8).unpack("q>").first
29
29
  @pos += 8
30
30
  long
31
31
  end
32
32
 
33
33
  def string
34
34
  len = int16
35
- string = @s.slice(@pos, len)
35
+ string = @s.byteslice(@pos, len)
36
36
  @pos += len
37
37
  string
38
38
  end
39
39
 
40
40
  def read(bytes)
41
- data = @s.slice(@pos, bytes)
41
+ data = @s.byteslice(@pos, bytes)
42
42
  @pos += bytes
43
43
  data
44
44
  end
45
45
 
46
46
  def peek(bytes)
47
- @s.slice(@pos, bytes)
47
+ @s.byteslice(@pos, bytes)
48
48
  end
49
49
 
50
50
  def bytes
@@ -69,10 +69,12 @@ module Poseidon
69
69
  end
70
70
  end
71
71
 
72
- def shutdown
73
- @broker_pool.shutdown
72
+ def close
73
+ @broker_pool.close
74
74
  end
75
-
75
+
76
+ alias_method :shutdown, :close
77
+
76
78
  private
77
79
 
78
80
  def ensure_metadata_available_for_topics(messages_to_send)
@@ -1,4 +1,4 @@
1
1
  module Poseidon
2
2
  # Unstable! API May Change!
3
- VERSION = "0.0.5.pre1"
3
+ VERSION = "0.0.5"
4
4
  end
@@ -19,7 +19,8 @@ Gem::Specification.new do |gem|
19
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
20
  gem.require_paths = ["lib"]
21
21
 
22
- gem.add_development_dependency(%q<rspec>, '~> 2.13.0')
22
+ gem.add_development_dependency(%q<rspec>, '>= 3')
23
23
  gem.add_development_dependency(%q<yard>)
24
24
  gem.add_development_dependency(%q<simplecov>)
25
+ gem.add_development_dependency(%q<snappy>)
25
26
  end
@@ -1,6 +1,6 @@
1
1
  require 'integration/multiple_brokers/spec_helper'
2
2
 
3
- describe "consuming with multiple brokers" do
3
+ RSpec.describe "consuming with multiple brokers", :type => :request do
4
4
  before(:each) do
5
5
  # autocreate the topic by asking for information about it
6
6
  c = Connection.new("localhost", 9092, "metadata_fetcher", 10_000)
@@ -1,6 +1,6 @@
1
1
  require 'integration/multiple_brokers/spec_helper'
2
2
 
3
- describe "handling failures" do
3
+ RSpec.describe "handling failures", :type => :request do
4
4
  describe "metadata failures" do
5
5
  before(:each) do
6
6
  @messages_to_send = [
@@ -1,6 +1,6 @@
1
1
  require 'integration/multiple_brokers/spec_helper'
2
2
 
3
- describe "producer handles rebalancing" do
3
+ RSpec.describe "producer handles rebalancing", :type => :request do
4
4
  before(:each) do
5
5
  # autocreate the topic by asking for information about it
6
6
  @c = Connection.new("localhost", 9093, "metadata_fetcher", 10_000)