ruby-kafka-custom 0.7.7.26

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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/lib/kafka/async_producer.rb +279 -0
  3. data/lib/kafka/broker.rb +205 -0
  4. data/lib/kafka/broker_info.rb +16 -0
  5. data/lib/kafka/broker_pool.rb +41 -0
  6. data/lib/kafka/broker_uri.rb +43 -0
  7. data/lib/kafka/client.rb +754 -0
  8. data/lib/kafka/cluster.rb +455 -0
  9. data/lib/kafka/compression.rb +43 -0
  10. data/lib/kafka/compressor.rb +85 -0
  11. data/lib/kafka/connection.rb +220 -0
  12. data/lib/kafka/connection_builder.rb +33 -0
  13. data/lib/kafka/consumer.rb +592 -0
  14. data/lib/kafka/consumer_group.rb +208 -0
  15. data/lib/kafka/datadog.rb +413 -0
  16. data/lib/kafka/fetch_operation.rb +115 -0
  17. data/lib/kafka/fetched_batch.rb +54 -0
  18. data/lib/kafka/fetched_batch_generator.rb +117 -0
  19. data/lib/kafka/fetched_message.rb +47 -0
  20. data/lib/kafka/fetched_offset_resolver.rb +48 -0
  21. data/lib/kafka/fetcher.rb +221 -0
  22. data/lib/kafka/gzip_codec.rb +30 -0
  23. data/lib/kafka/heartbeat.rb +25 -0
  24. data/lib/kafka/instrumenter.rb +38 -0
  25. data/lib/kafka/lz4_codec.rb +23 -0
  26. data/lib/kafka/message_buffer.rb +87 -0
  27. data/lib/kafka/offset_manager.rb +248 -0
  28. data/lib/kafka/partitioner.rb +35 -0
  29. data/lib/kafka/pause.rb +92 -0
  30. data/lib/kafka/pending_message.rb +29 -0
  31. data/lib/kafka/pending_message_queue.rb +41 -0
  32. data/lib/kafka/produce_operation.rb +205 -0
  33. data/lib/kafka/producer.rb +504 -0
  34. data/lib/kafka/protocol.rb +217 -0
  35. data/lib/kafka/protocol/add_partitions_to_txn_request.rb +34 -0
  36. data/lib/kafka/protocol/add_partitions_to_txn_response.rb +47 -0
  37. data/lib/kafka/protocol/alter_configs_request.rb +44 -0
  38. data/lib/kafka/protocol/alter_configs_response.rb +49 -0
  39. data/lib/kafka/protocol/api_versions_request.rb +21 -0
  40. data/lib/kafka/protocol/api_versions_response.rb +53 -0
  41. data/lib/kafka/protocol/consumer_group_protocol.rb +19 -0
  42. data/lib/kafka/protocol/create_partitions_request.rb +42 -0
  43. data/lib/kafka/protocol/create_partitions_response.rb +28 -0
  44. data/lib/kafka/protocol/create_topics_request.rb +45 -0
  45. data/lib/kafka/protocol/create_topics_response.rb +26 -0
  46. data/lib/kafka/protocol/decoder.rb +175 -0
  47. data/lib/kafka/protocol/delete_topics_request.rb +33 -0
  48. data/lib/kafka/protocol/delete_topics_response.rb +26 -0
  49. data/lib/kafka/protocol/describe_configs_request.rb +35 -0
  50. data/lib/kafka/protocol/describe_configs_response.rb +73 -0
  51. data/lib/kafka/protocol/describe_groups_request.rb +27 -0
  52. data/lib/kafka/protocol/describe_groups_response.rb +73 -0
  53. data/lib/kafka/protocol/encoder.rb +184 -0
  54. data/lib/kafka/protocol/end_txn_request.rb +29 -0
  55. data/lib/kafka/protocol/end_txn_response.rb +19 -0
  56. data/lib/kafka/protocol/fetch_request.rb +70 -0
  57. data/lib/kafka/protocol/fetch_response.rb +136 -0
  58. data/lib/kafka/protocol/find_coordinator_request.rb +29 -0
  59. data/lib/kafka/protocol/find_coordinator_response.rb +29 -0
  60. data/lib/kafka/protocol/heartbeat_request.rb +27 -0
  61. data/lib/kafka/protocol/heartbeat_response.rb +17 -0
  62. data/lib/kafka/protocol/init_producer_id_request.rb +26 -0
  63. data/lib/kafka/protocol/init_producer_id_response.rb +27 -0
  64. data/lib/kafka/protocol/join_group_request.rb +41 -0
  65. data/lib/kafka/protocol/join_group_response.rb +33 -0
  66. data/lib/kafka/protocol/leave_group_request.rb +25 -0
  67. data/lib/kafka/protocol/leave_group_response.rb +17 -0
  68. data/lib/kafka/protocol/list_groups_request.rb +23 -0
  69. data/lib/kafka/protocol/list_groups_response.rb +35 -0
  70. data/lib/kafka/protocol/list_offset_request.rb +53 -0
  71. data/lib/kafka/protocol/list_offset_response.rb +89 -0
  72. data/lib/kafka/protocol/member_assignment.rb +42 -0
  73. data/lib/kafka/protocol/message.rb +172 -0
  74. data/lib/kafka/protocol/message_set.rb +55 -0
  75. data/lib/kafka/protocol/metadata_request.rb +31 -0
  76. data/lib/kafka/protocol/metadata_response.rb +185 -0
  77. data/lib/kafka/protocol/offset_commit_request.rb +47 -0
  78. data/lib/kafka/protocol/offset_commit_response.rb +29 -0
  79. data/lib/kafka/protocol/offset_fetch_request.rb +36 -0
  80. data/lib/kafka/protocol/offset_fetch_response.rb +56 -0
  81. data/lib/kafka/protocol/produce_request.rb +92 -0
  82. data/lib/kafka/protocol/produce_response.rb +63 -0
  83. data/lib/kafka/protocol/record.rb +88 -0
  84. data/lib/kafka/protocol/record_batch.rb +222 -0
  85. data/lib/kafka/protocol/request_message.rb +26 -0
  86. data/lib/kafka/protocol/sasl_handshake_request.rb +33 -0
  87. data/lib/kafka/protocol/sasl_handshake_response.rb +28 -0
  88. data/lib/kafka/protocol/sync_group_request.rb +33 -0
  89. data/lib/kafka/protocol/sync_group_response.rb +23 -0
  90. data/lib/kafka/round_robin_assignment_strategy.rb +54 -0
  91. data/lib/kafka/sasl/gssapi.rb +76 -0
  92. data/lib/kafka/sasl/oauth.rb +64 -0
  93. data/lib/kafka/sasl/plain.rb +39 -0
  94. data/lib/kafka/sasl/scram.rb +177 -0
  95. data/lib/kafka/sasl_authenticator.rb +61 -0
  96. data/lib/kafka/snappy_codec.rb +25 -0
  97. data/lib/kafka/socket_with_timeout.rb +96 -0
  98. data/lib/kafka/ssl_context.rb +66 -0
  99. data/lib/kafka/ssl_socket_with_timeout.rb +187 -0
  100. data/lib/kafka/statsd.rb +296 -0
  101. data/lib/kafka/tagged_logger.rb +72 -0
  102. data/lib/kafka/transaction_manager.rb +261 -0
  103. data/lib/kafka/transaction_state_machine.rb +72 -0
  104. data/lib/kafka/version.rb +5 -0
  105. metadata +461 -0
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kafka
4
+ module Protocol
5
+ class MemberAssignment
6
+ attr_reader :topics
7
+
8
+ def initialize(version: 0, topics: {}, user_data: nil)
9
+ @version = version
10
+ @topics = topics
11
+ @user_data = user_data
12
+ end
13
+
14
+ def assign(topic, partitions)
15
+ @topics[topic] ||= []
16
+ @topics[topic].concat(partitions)
17
+ end
18
+
19
+ def encode(encoder)
20
+ encoder.write_int16(@version)
21
+
22
+ encoder.write_array(@topics) do |topic, partitions|
23
+ encoder.write_string(topic)
24
+
25
+ encoder.write_array(partitions) do |partition|
26
+ encoder.write_int32(partition)
27
+ end
28
+ end
29
+
30
+ encoder.write_bytes(@user_data)
31
+ end
32
+
33
+ def self.decode(decoder)
34
+ new(
35
+ version: decoder.int16,
36
+ topics: Hash[decoder.array { [decoder.string, decoder.array { decoder.int32 }] }],
37
+ user_data: decoder.bytes,
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require "zlib"
5
+
6
+ module Kafka
7
+ module Protocol
8
+
9
+ # ## API Specification
10
+ #
11
+ # Message => Crc MagicByte Attributes Timestamp Key Value
12
+ # Crc => int32
13
+ # MagicByte => int8
14
+ # Attributes => int8
15
+ # Timestamp => int64, in ms
16
+ # Key => bytes
17
+ # Value => bytes
18
+ #
19
+ class Message
20
+ MAGIC_BYTE = 1
21
+
22
+ attr_reader :key, :value, :codec_id, :offset
23
+
24
+ attr_reader :bytesize, :create_time
25
+
26
+ def initialize(value:, key: nil, create_time: Time.now, codec_id: 0, offset: -1)
27
+ @key = key
28
+ @value = value
29
+ @codec_id = codec_id
30
+ @offset = offset
31
+ @create_time = create_time
32
+
33
+ @bytesize = @key.to_s.bytesize + @value.to_s.bytesize
34
+ end
35
+
36
+ def encode(encoder)
37
+ data = encode_with_crc
38
+
39
+ encoder.write_int64(offset)
40
+ encoder.write_bytes(data)
41
+ end
42
+
43
+ def ==(other)
44
+ @key == other.key &&
45
+ @value == other.value &&
46
+ @codec_id == other.codec_id &&
47
+ @offset == other.offset
48
+ end
49
+
50
+ def compressed?
51
+ @codec_id != 0
52
+ end
53
+
54
+ # @return [Array<Kafka::Protocol::Message>]
55
+ def decompress
56
+ codec = Compression.find_codec_by_id(@codec_id)
57
+
58
+ # For some weird reason we need to cut out the first 20 bytes.
59
+ data = codec.decompress(value)
60
+ message_set_decoder = Decoder.from_string(data)
61
+ message_set = MessageSet.decode(message_set_decoder)
62
+
63
+ correct_offsets(message_set.messages)
64
+ end
65
+
66
+ def self.decode(decoder)
67
+ offset = decoder.int64
68
+ message_decoder = Decoder.from_string(decoder.bytes)
69
+
70
+ _crc = message_decoder.int32
71
+ magic_byte = message_decoder.int8
72
+ attributes = message_decoder.int8
73
+
74
+ # The magic byte indicates the message format version. There are situations
75
+ # where an old message format can be returned from a newer version of Kafka,
76
+ # because old messages are not necessarily rewritten on upgrades.
77
+ case magic_byte
78
+ when 0
79
+ # No timestamp in the pre-0.10 message format.
80
+ timestamp = nil
81
+ when 1
82
+ timestamp = message_decoder.int64
83
+
84
+ # If the timestamp is set to zero, it's because the message has been upgraded
85
+ # from the Kafka 0.9 disk format to the Kafka 0.10 format. The former didn't
86
+ # have a timestamp attribute, so we'll just set the timestamp to nil.
87
+ timestamp = nil if timestamp.zero?
88
+ else
89
+ raise Kafka::Error, "Invalid magic byte: #{magic_byte}"
90
+ end
91
+
92
+ key = message_decoder.bytes
93
+ value = message_decoder.bytes
94
+
95
+ # The codec id is encoded in the three least significant bits of the
96
+ # attributes.
97
+ codec_id = attributes & 0b111
98
+
99
+ # The timestamp will be nil if the message was written in the Kafka 0.9 log format.
100
+ create_time = timestamp && Time.at(timestamp / 1000.0)
101
+
102
+ new(key: key, value: value, codec_id: codec_id, offset: offset, create_time: create_time)
103
+ end
104
+
105
+ # Ensure the backward compatibility of Message format from Kafka 0.11.x
106
+ def is_control_record
107
+ false
108
+ end
109
+
110
+ def headers
111
+ {}
112
+ end
113
+
114
+ private
115
+
116
+ # Offsets may be relative with regards to wrapped message offset, but there are special cases.
117
+ #
118
+ # Cases when client will receive corrected offsets:
119
+ # - When fetch request is version 0, kafka will correct relative offset on broker side before replying fetch response
120
+ # - When messages is stored in 0.9 format on disk (broker configured to do so).
121
+ #
122
+ # All other cases, compressed inner messages should have relative offset, with below attributes:
123
+ # - The container message should have the 'real' offset
124
+ # - The container message's offset should be the 'real' offset of the last message in the compressed batch
125
+ def correct_offsets(messages)
126
+ max_relative_offset = messages.last.offset
127
+
128
+ # The offsets are already correct, do nothing.
129
+ return messages if max_relative_offset == offset
130
+
131
+ # The contained messages have relative offsets, and needs to be corrected.
132
+ base_offset = offset - max_relative_offset
133
+
134
+ messages.map do |message|
135
+ Message.new(
136
+ offset: message.offset + base_offset,
137
+ value: message.value,
138
+ key: message.key,
139
+ create_time: message.create_time,
140
+ codec_id: message.codec_id
141
+ )
142
+ end
143
+ end
144
+
145
+ def encode_with_crc
146
+ buffer = StringIO.new
147
+ encoder = Encoder.new(buffer)
148
+
149
+ data = encode_without_crc
150
+ crc = Zlib.crc32(data)
151
+
152
+ encoder.write_int32(crc)
153
+ encoder.write(data)
154
+
155
+ buffer.string
156
+ end
157
+
158
+ def encode_without_crc
159
+ buffer = StringIO.new
160
+ encoder = Encoder.new(buffer)
161
+
162
+ encoder.write_int8(MAGIC_BYTE)
163
+ encoder.write_int8(@codec_id)
164
+ encoder.write_int64((@create_time.to_f * 1000).to_i)
165
+ encoder.write_bytes(@key)
166
+ encoder.write_bytes(@value)
167
+
168
+ buffer.string
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kafka
4
+ module Protocol
5
+ class MessageSet
6
+ attr_reader :messages
7
+
8
+ def initialize(messages: [])
9
+ @messages = messages
10
+ end
11
+
12
+ def size
13
+ @messages.size
14
+ end
15
+
16
+ def ==(other)
17
+ messages == other.messages
18
+ end
19
+
20
+ def encode(encoder)
21
+ # Messages in a message set are *not* encoded as an array. Rather,
22
+ # they are written in sequence.
23
+ @messages.each do |message|
24
+ message.encode(encoder)
25
+ end
26
+ end
27
+
28
+ def self.decode(decoder)
29
+ fetched_messages = []
30
+
31
+ until decoder.eof?
32
+ begin
33
+ message = Message.decode(decoder)
34
+
35
+ if message.compressed?
36
+ fetched_messages.concat(message.decompress)
37
+ else
38
+ fetched_messages << message
39
+ end
40
+ rescue EOFError
41
+ if fetched_messages.empty?
42
+ # If the first message in the set is truncated, it's likely because the
43
+ # message is larger than the maximum size that we have asked for.
44
+ raise MessageTooLargeToRead
45
+ else
46
+ # We tried to decode a partial message at the end of the set; just skip it.
47
+ end
48
+ end
49
+ end
50
+
51
+ new(messages: fetched_messages)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kafka
4
+ module Protocol
5
+ class MetadataRequest
6
+
7
+ # A request for cluster metadata.
8
+ #
9
+ # @param topics [Array<String>]
10
+ def initialize(topics: [])
11
+ @topics = topics
12
+ end
13
+
14
+ def api_key
15
+ TOPIC_METADATA_API
16
+ end
17
+
18
+ def api_version
19
+ 1
20
+ end
21
+
22
+ def response_class
23
+ Protocol::MetadataResponse
24
+ end
25
+
26
+ def encode(encoder)
27
+ encoder.write_array(@topics) {|topic| encoder.write_string(topic) }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kafka
4
+ module Protocol
5
+
6
+ # A response to a {MetadataRequest}.
7
+ #
8
+ # The response contains information on the brokers, topics, and partitions in
9
+ # the cluster.
10
+ #
11
+ # * For each broker a node id, host, and port is provided.
12
+ # * For each topic partition the node id of the broker acting as partition leader,
13
+ # as well as a list of node ids for the set of replicas, are given. The `isr` list is
14
+ # the subset of replicas that are "in sync", i.e. have fully caught up with the
15
+ # leader.
16
+ #
17
+ # ## API Specification
18
+ #
19
+ # MetadataResponse => [Broker][TopicMetadata]
20
+ # Broker => NodeId Host Port (any number of brokers may be returned)
21
+ # NodeId => int32
22
+ # Host => string
23
+ # Port => int32
24
+ #
25
+ # TopicMetadata => TopicErrorCode TopicName [PartitionMetadata]
26
+ # TopicErrorCode => int16
27
+ #
28
+ # PartitionMetadata => PartitionErrorCode PartitionId Leader Replicas Isr
29
+ # PartitionErrorCode => int16
30
+ # PartitionId => int32
31
+ # Leader => int32
32
+ # Replicas => [int32]
33
+ # Isr => [int32]
34
+ #
35
+ class MetadataResponse
36
+ class PartitionMetadata
37
+ attr_reader :partition_id, :leader
38
+
39
+ attr_reader :partition_error_code
40
+
41
+ def initialize(partition_error_code:, partition_id:, leader:, replicas: [], isr: [])
42
+ @partition_error_code = partition_error_code
43
+ @partition_id = partition_id
44
+ @leader = leader
45
+ @replicas = replicas
46
+ @isr = isr
47
+ end
48
+ end
49
+
50
+ class TopicMetadata
51
+ # @return [String] the name of the topic
52
+ attr_reader :topic_name
53
+
54
+ # @return [Array<PartitionMetadata>] the partitions in the topic.
55
+ attr_reader :partitions
56
+
57
+ attr_reader :topic_error_code
58
+
59
+ def initialize(topic_error_code: 0, topic_name:, partitions:)
60
+ @topic_error_code = topic_error_code
61
+ @topic_name = topic_name
62
+ @partitions = partitions
63
+ end
64
+ end
65
+
66
+ # @return [Array<Kafka::BrokerInfo>] the list of brokers in the cluster.
67
+ attr_reader :brokers
68
+
69
+ # @return [Array<TopicMetadata>] the list of topics in the cluster.
70
+ attr_reader :topics
71
+
72
+ # @return [Integer] The broker id of the controller broker.
73
+ attr_reader :controller_id
74
+
75
+ def initialize(brokers:, controller_id:, topics:)
76
+ @brokers = brokers
77
+ @controller_id = controller_id
78
+ @topics = topics
79
+ end
80
+
81
+ # Finds the node id of the broker that is acting as leader for the given topic
82
+ # and partition per this metadata.
83
+ #
84
+ # @param topic [String] the name of the topic.
85
+ # @param partition [Integer] the partition number.
86
+ # @return [Integer] the node id of the leader.
87
+ def find_leader_id(topic, partition)
88
+ topic_info = @topics.find {|t| t.topic_name == topic }
89
+
90
+ if topic_info.nil?
91
+ raise UnknownTopicOrPartition, "no topic #{topic}"
92
+ end
93
+
94
+ Protocol.handle_error(topic_info.topic_error_code)
95
+
96
+ partition_info = topic_info.partitions.find {|p| p.partition_id == partition }
97
+
98
+ if partition_info.nil?
99
+ raise UnknownTopicOrPartition, "no partition #{partition} in topic #{topic}"
100
+ end
101
+
102
+ begin
103
+ Protocol.handle_error(partition_info.partition_error_code)
104
+ rescue ReplicaNotAvailable
105
+ # This error can be safely ignored per the protocol specification.
106
+ end
107
+
108
+ partition_info.leader
109
+ end
110
+
111
+ # Finds the broker info for the given node id.
112
+ #
113
+ # @param node_id [Integer] the node id of the broker.
114
+ # @return [Kafka::BrokerInfo] information about the broker.
115
+ def find_broker(node_id)
116
+ broker = @brokers.find {|b| b.node_id == node_id }
117
+
118
+ raise Kafka::NoSuchBroker, "No broker with id #{node_id}" if broker.nil?
119
+
120
+ broker
121
+ end
122
+
123
+ def controller_broker
124
+ find_broker(controller_id)
125
+ end
126
+
127
+ def partitions_for(topic_name)
128
+ topic = @topics.find {|t| t.topic_name == topic_name }
129
+
130
+ if topic.nil?
131
+ raise UnknownTopicOrPartition, "unknown topic #{topic_name}"
132
+ end
133
+
134
+ Protocol.handle_error(topic.topic_error_code)
135
+
136
+ topic.partitions
137
+ end
138
+
139
+ # Decodes a MetadataResponse from a {Decoder} containing response data.
140
+ #
141
+ # @param decoder [Decoder]
142
+ # @return [MetadataResponse] the metadata response.
143
+ def self.decode(decoder)
144
+ brokers = decoder.array do
145
+ node_id = decoder.int32
146
+ host = decoder.string
147
+ port = decoder.int32
148
+ _rack = decoder.string
149
+
150
+ BrokerInfo.new(
151
+ node_id: node_id,
152
+ host: host,
153
+ port: port
154
+ )
155
+ end
156
+
157
+ controller_id = decoder.int32
158
+
159
+ topics = decoder.array do
160
+ topic_error_code = decoder.int16
161
+ topic_name = decoder.string
162
+ _is_internal = decoder.boolean
163
+
164
+ partitions = decoder.array do
165
+ PartitionMetadata.new(
166
+ partition_error_code: decoder.int16,
167
+ partition_id: decoder.int32,
168
+ leader: decoder.int32,
169
+ replicas: decoder.array { decoder.int32 },
170
+ isr: decoder.array { decoder.int32 },
171
+ )
172
+ end
173
+
174
+ TopicMetadata.new(
175
+ topic_error_code: topic_error_code,
176
+ topic_name: topic_name,
177
+ partitions: partitions,
178
+ )
179
+ end
180
+
181
+ new(brokers: brokers, controller_id: controller_id, topics: topics)
182
+ end
183
+ end
184
+ end
185
+ end