deimos-kafka 1.0.0.pre.beta15

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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +74 -0
  3. data/.gitignore +41 -0
  4. data/.gitmodules +0 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +321 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/CHANGELOG.md +9 -0
  10. data/CODE_OF_CONDUCT.md +77 -0
  11. data/Dockerfile +23 -0
  12. data/Gemfile +6 -0
  13. data/Gemfile.lock +165 -0
  14. data/Guardfile +22 -0
  15. data/LICENSE.md +195 -0
  16. data/README.md +742 -0
  17. data/Rakefile +13 -0
  18. data/bin/deimos +4 -0
  19. data/deimos-kafka.gemspec +42 -0
  20. data/docker-compose.yml +71 -0
  21. data/docs/DATABASE_BACKEND.md +147 -0
  22. data/docs/PULL_REQUEST_TEMPLATE.md +34 -0
  23. data/lib/deimos.rb +134 -0
  24. data/lib/deimos/active_record_consumer.rb +81 -0
  25. data/lib/deimos/active_record_producer.rb +64 -0
  26. data/lib/deimos/avro_data_coder.rb +89 -0
  27. data/lib/deimos/avro_data_decoder.rb +36 -0
  28. data/lib/deimos/avro_data_encoder.rb +51 -0
  29. data/lib/deimos/backends/db.rb +27 -0
  30. data/lib/deimos/backends/kafka.rb +27 -0
  31. data/lib/deimos/backends/kafka_async.rb +27 -0
  32. data/lib/deimos/configuration.rb +88 -0
  33. data/lib/deimos/consumer.rb +164 -0
  34. data/lib/deimos/instrumentation.rb +71 -0
  35. data/lib/deimos/kafka_message.rb +27 -0
  36. data/lib/deimos/kafka_source.rb +126 -0
  37. data/lib/deimos/kafka_topic_info.rb +79 -0
  38. data/lib/deimos/message.rb +74 -0
  39. data/lib/deimos/metrics/datadog.rb +47 -0
  40. data/lib/deimos/metrics/mock.rb +39 -0
  41. data/lib/deimos/metrics/provider.rb +38 -0
  42. data/lib/deimos/monkey_patches/phobos_cli.rb +35 -0
  43. data/lib/deimos/monkey_patches/phobos_producer.rb +51 -0
  44. data/lib/deimos/monkey_patches/ruby_kafka_heartbeat.rb +85 -0
  45. data/lib/deimos/monkey_patches/schema_store.rb +19 -0
  46. data/lib/deimos/producer.rb +218 -0
  47. data/lib/deimos/publish_backend.rb +30 -0
  48. data/lib/deimos/railtie.rb +8 -0
  49. data/lib/deimos/schema_coercer.rb +108 -0
  50. data/lib/deimos/shared_config.rb +59 -0
  51. data/lib/deimos/test_helpers.rb +356 -0
  52. data/lib/deimos/tracing/datadog.rb +35 -0
  53. data/lib/deimos/tracing/mock.rb +40 -0
  54. data/lib/deimos/tracing/provider.rb +31 -0
  55. data/lib/deimos/utils/db_producer.rb +95 -0
  56. data/lib/deimos/utils/executor.rb +117 -0
  57. data/lib/deimos/utils/inline_consumer.rb +144 -0
  58. data/lib/deimos/utils/lag_reporter.rb +182 -0
  59. data/lib/deimos/utils/platform_schema_validation.rb +0 -0
  60. data/lib/deimos/utils/signal_handler.rb +68 -0
  61. data/lib/deimos/version.rb +5 -0
  62. data/lib/generators/deimos/db_backend/templates/migration +24 -0
  63. data/lib/generators/deimos/db_backend/templates/rails3_migration +30 -0
  64. data/lib/generators/deimos/db_backend_generator.rb +48 -0
  65. data/lib/tasks/deimos.rake +17 -0
  66. data/spec/active_record_consumer_spec.rb +81 -0
  67. data/spec/active_record_producer_spec.rb +107 -0
  68. data/spec/avro_data_decoder_spec.rb +18 -0
  69. data/spec/avro_data_encoder_spec.rb +37 -0
  70. data/spec/backends/db_spec.rb +35 -0
  71. data/spec/backends/kafka_async_spec.rb +11 -0
  72. data/spec/backends/kafka_spec.rb +11 -0
  73. data/spec/consumer_spec.rb +169 -0
  74. data/spec/deimos_spec.rb +117 -0
  75. data/spec/kafka_source_spec.rb +168 -0
  76. data/spec/kafka_topic_info_spec.rb +88 -0
  77. data/spec/phobos.bad_db.yml +73 -0
  78. data/spec/phobos.yml +73 -0
  79. data/spec/producer_spec.rb +397 -0
  80. data/spec/publish_backend_spec.rb +10 -0
  81. data/spec/schemas/com/my-namespace/MySchema-key.avsc +13 -0
  82. data/spec/schemas/com/my-namespace/MySchema.avsc +18 -0
  83. data/spec/schemas/com/my-namespace/MySchemaWithBooleans.avsc +18 -0
  84. data/spec/schemas/com/my-namespace/MySchemaWithDateTimes.avsc +33 -0
  85. data/spec/schemas/com/my-namespace/MySchemaWithId.avsc +28 -0
  86. data/spec/schemas/com/my-namespace/MySchemaWithUniqueId.avsc +32 -0
  87. data/spec/schemas/com/my-namespace/Widget.avsc +27 -0
  88. data/spec/schemas/com/my-namespace/WidgetTheSecond.avsc +27 -0
  89. data/spec/spec_helper.rb +207 -0
  90. data/spec/updateable_schema_store_spec.rb +36 -0
  91. data/spec/utils/db_producer_spec.rb +208 -0
  92. data/spec/utils/executor_spec.rb +42 -0
  93. data/spec/utils/lag_reporter_spec.rb +69 -0
  94. data/spec/utils/platform_schema_validation_spec.rb +0 -0
  95. data/spec/utils/signal_handler_spec.rb +16 -0
  96. data/support/deimos-solo.png +0 -0
  97. data/support/deimos-with-name-next.png +0 -0
  98. data/support/deimos-with-name.png +0 -0
  99. data/support/flipp-logo.png +0 -0
  100. metadata +452 -0
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Deimos::PublishBackend do
4
+ include_context 'with publish_backend'
5
+ it 'should call execute' do
6
+ expect(described_class).to receive(:execute).
7
+ with(messages: messages, producer_class: MyProducer)
8
+ described_class.publish(producer_class: MyProducer, messages: messages)
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "MySchema-key",
4
+ "type": "record",
5
+ "doc": "Test schema",
6
+ "fields": [
7
+ {
8
+ "name": "test_id",
9
+ "type": "string",
10
+ "doc": "test string"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "MySchema",
4
+ "type": "record",
5
+ "doc": "Test schema",
6
+ "fields": [
7
+ {
8
+ "name": "test_id",
9
+ "type": "string",
10
+ "doc": "test string"
11
+ },
12
+ {
13
+ "name": "some_int",
14
+ "type": "int",
15
+ "doc": "test int"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "MySchemaWithBooleans",
4
+ "type": "record",
5
+ "doc": "Test schema",
6
+ "fields": [
7
+ {
8
+ "name": "test_id",
9
+ "type": "string",
10
+ "doc": "test string"
11
+ },
12
+ {
13
+ "name": "some_bool",
14
+ "type": "boolean",
15
+ "doc": "test bool"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "MySchemaWithDateTimes",
4
+ "type": "record",
5
+ "doc": "Test schema",
6
+ "fields": [
7
+ {
8
+ "name": "test_id",
9
+ "type": "string",
10
+ "doc": "test string"
11
+ },
12
+ {
13
+ "name": "updated_at",
14
+ "type": ["int", "null"]
15
+ },
16
+ {
17
+ "name": "some_int",
18
+ "type": ["null", "int"],
19
+ "doc": "test int"
20
+ },
21
+ {
22
+ "name": "some_datetime_int",
23
+ "type": ["null", "int"],
24
+ "doc": "test datetime"
25
+ },
26
+ {
27
+ "name": "timestamp",
28
+ "type": "string",
29
+ "doc": "ISO timestamp",
30
+ "default": ""
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "MySchemaWithId",
4
+ "type": "record",
5
+ "doc": "Test schema",
6
+ "fields": [
7
+ {
8
+ "name": "test_id",
9
+ "type": "string",
10
+ "doc": "test string"
11
+ },
12
+ {
13
+ "name": "some_int",
14
+ "type": "int",
15
+ "doc": "test int"
16
+ },
17
+ {
18
+ "name": "message_id",
19
+ "type": "string",
20
+ "doc": "UUID"
21
+ },
22
+ {
23
+ "name": "timestamp",
24
+ "type": "string",
25
+ "doc": "timestamp"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "MySchemaWithUniqueId",
4
+ "type": "record",
5
+ "doc": "Test schema",
6
+ "fields": [
7
+ {
8
+ "name": "id",
9
+ "type": "int"
10
+ },
11
+ {
12
+ "name": "test_id",
13
+ "type": "string",
14
+ "doc": "test string"
15
+ },
16
+ {
17
+ "name": "some_int",
18
+ "type": "int",
19
+ "doc": "test int"
20
+ },
21
+ {
22
+ "name": "message_id",
23
+ "type": "string",
24
+ "doc": "UUID"
25
+ },
26
+ {
27
+ "name": "timestamp",
28
+ "type": "string",
29
+ "doc": "timestamp"
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "Widget",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "id",
8
+ "type": "long"
9
+ },
10
+ {
11
+ "name": "widget_id",
12
+ "type": "long"
13
+ },
14
+ {
15
+ "name": "name",
16
+ "type": "string"
17
+ },
18
+ {
19
+ "name": "updated_at",
20
+ "type": "long"
21
+ },
22
+ {
23
+ "name": "created_at",
24
+ "type": "long"
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "WidgetTheSecond",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "id",
8
+ "type": "long"
9
+ },
10
+ {
11
+ "name": "widget_id",
12
+ "type": "long"
13
+ },
14
+ {
15
+ "name": "model_id",
16
+ "type": "string"
17
+ },
18
+ {
19
+ "name": "updated_at",
20
+ "type": "long"
21
+ },
22
+ {
23
+ "name": "created_at",
24
+ "type": "long"
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
4
+ require 'active_record'
5
+ require 'deimos'
6
+ require 'deimos/metrics/mock'
7
+ require 'deimos/tracing/mock'
8
+ require 'deimos/test_helpers'
9
+ require 'active_support/testing/time_helpers'
10
+
11
+ # Helpers for Executor/DbProducer
12
+ module TestRunners
13
+ # Execute a block until it stops failing. This is helpful for testing threads
14
+ # where we need to wait for them to continue but don't want to rely on
15
+ # sleeping for X seconds, which is crazy brittle and slow.
16
+ def wait_for
17
+ start_time = Time.now
18
+ begin
19
+ yield
20
+ rescue Exception # rubocop:disable Lint/RescueException
21
+ raise if Time.now - start_time > 2 # 2 seconds is probably plenty of time! <_<
22
+
23
+ sleep(0.1)
24
+ retry
25
+ end
26
+ end
27
+
28
+ # Test runner
29
+ class TestRunner
30
+ attr_accessor :id, :started, :stopped, :should_error
31
+ # :nodoc:
32
+ def initialize(id=nil)
33
+ @id = id
34
+ end
35
+
36
+ # :nodoc:
37
+ def start
38
+ if @should_error
39
+ @should_error = false
40
+ raise 'OH NOES'
41
+ end
42
+ @started = true
43
+ end
44
+
45
+ # :nodoc:
46
+ def stop
47
+ @stopped = true
48
+ end
49
+ end
50
+ end
51
+
52
+ # :nodoc:
53
+ module DbConfigs
54
+ # @param payload [Hash]
55
+ # @param topic [String]
56
+ # @param key [String]
57
+ def build_message(payload, topic, key)
58
+ message = Deimos::Message.new(payload, Deimos::Producer,
59
+ topic: topic, key: key)
60
+ message.encoded_payload = message.payload
61
+ message.encoded_key = message.key
62
+ message
63
+ end
64
+
65
+ DB_OPTIONS = [
66
+ {
67
+ adapter: 'postgresql',
68
+ port: 5432,
69
+ username: 'postgres',
70
+ password: 'root',
71
+ database: 'postgres',
72
+ host: ENV['PG_HOST'] || 'localhost'
73
+ },
74
+ {
75
+ adapter: 'mysql2',
76
+ port: 3306,
77
+ username: 'root',
78
+ database: 'test',
79
+ host: ENV['MYSQL_HOST'] || 'localhost'
80
+ },
81
+ {
82
+ adapter: 'sqlite3',
83
+ database: 'test.sqlite3'
84
+ } # this one always needs to be last for non-integration tests
85
+ ].freeze
86
+
87
+ # For each config, run some tests.
88
+ def each_db_config(subject, &block)
89
+ DB_OPTIONS.each do |options|
90
+ describe subject, :integration, db_config: options do
91
+
92
+ include_context 'with DB'
93
+ describe options[:adapter] do # rubocop:disable RSpec/EmptyExampleGroup
94
+ self.instance_eval(&block)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ # Set up the given database.
101
+ def setup_db(options)
102
+ ActiveRecord::Base.establish_connection(options)
103
+ migration_class_name = 'DbBackendMigration'
104
+ migration_version = '[5.2]'
105
+ migration = ERB.new(
106
+ File.read('lib/generators/deimos/db_backend/templates/migration')
107
+ ).result(binding)
108
+ eval(migration) # rubocop:disable Security/Eval
109
+ ActiveRecord::Migration.new.run(DbBackendMigration, direction: :up)
110
+
111
+ ActiveRecord::Base.descendants.each do |klass|
112
+ klass.reset_sequence_name if klass.respond_to?(:reset_sequence_name)
113
+ # reset internal variables - terrible hack to trick Rails into doing this
114
+ table_name = klass.table_name
115
+ klass.table_name = "#{table_name}2"
116
+ klass.table_name = table_name
117
+ end
118
+ end
119
+ end
120
+
121
+ RSpec.configure do |config|
122
+ config.extend(DbConfigs)
123
+ include DbConfigs
124
+ config.include TestRunners
125
+ config.full_backtrace = true
126
+
127
+ # true by default for RSpec 4.0
128
+ config.shared_context_metadata_behavior = :apply_to_host_groups
129
+
130
+ config.before(:all) do
131
+ Time.zone = 'EST'
132
+ ActiveRecord::Base.logger = Logger.new('/dev/null')
133
+ ActiveRecord::Base.establish_connection(
134
+ 'adapter' => 'sqlite3',
135
+ 'database' => 'test.sqlite3'
136
+ )
137
+ end
138
+ config.include Deimos::TestHelpers
139
+ config.include ActiveSupport::Testing::TimeHelpers
140
+ config.before(:suite) do
141
+ Time.zone = 'EST'
142
+ ActiveRecord::Base.logger = Logger.new('/dev/null')
143
+ setup_db(DbConfigs::DB_OPTIONS.last)
144
+ Deimos.configure do |fr_config|
145
+ fr_config.phobos_config_file = File.join(File.dirname(__FILE__), 'phobos.yml')
146
+ fr_config.schema_path = File.join(File.expand_path(__dir__), 'schemas')
147
+ fr_config.reraise_consumer_errors = true
148
+ fr_config.schema_registry_url = ENV['SCHEMA_REGISTRY'] || 'http://localhost:8081'
149
+ fr_config.seed_broker = ENV['KAFKA_SEED_BROKER'] || 'localhost:9092'
150
+ fr_config.logger = Logger.new('/dev/null')
151
+
152
+ # Use Mock Metrics and Tracing for rspecs
153
+ fr_config.metrics = Deimos::Metrics::Mock.new
154
+ fr_config.tracer = Deimos::Tracing::Mock.new
155
+ end
156
+ end
157
+
158
+ config.before(:each) do |ex|
159
+ stub_producers_and_consumers! unless ex.metadata[:integration]
160
+
161
+ @previous_config = Deimos.config.dup
162
+ @previous_phobos_config = Phobos.config.dup
163
+ end
164
+
165
+ config.after(:each) do
166
+ Deimos.config = @previous_config
167
+ Phobos.instance_variable_set(:@config, @previous_phobos_config)
168
+ end
169
+
170
+ end
171
+
172
+ RSpec.shared_context('with DB') do
173
+ before(:all) do
174
+ setup_db(self.class.metadata[:db_config] || DbConfigs::DB_OPTIONS.last)
175
+ end
176
+
177
+ after(:each) do
178
+ Deimos::KafkaMessage.delete_all
179
+ Deimos::KafkaTopicInfo.delete_all
180
+ end
181
+ end
182
+
183
+ RSpec.shared_context('with publish_backend') do
184
+ before(:each) do
185
+ producer_class = Class.new(Deimos::Producer) do
186
+ schema 'MySchema'
187
+ namespace 'com.my-namespace'
188
+ topic 'my-topic'
189
+ key_config field: 'test_id'
190
+ end
191
+ stub_const('MyProducer', producer_class)
192
+
193
+ producer_class = Class.new(Deimos::Producer) do
194
+ schema 'MySchema'
195
+ namespace 'com.my-namespace'
196
+ topic 'my-topic'
197
+ key_config none: true
198
+ end
199
+ stub_const('MyNoKeyProducer', producer_class)
200
+ end
201
+
202
+ let(:messages) do
203
+ (1..3).map do |i|
204
+ build_message({ foo: i }, 'my-topic', "foo#{i}")
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe AvroTurf::SchemaStore do
4
+
5
+ it 'should add an in-memory schema' do
6
+ schema_store = described_class.new(path: Deimos.config.schema_path)
7
+ schema_store.load_schemas!
8
+ found_schema = schema_store.find('MySchema', 'com.my-namespace').as_json
9
+ expect(found_schema['name']).to eq('MySchema')
10
+ expect(found_schema['namespace']).to eq('com.my-namespace')
11
+ expect(found_schema['fields'].size).to eq(2)
12
+ expect(found_schema['fields'][0]['type']['type_sym']).to eq('string')
13
+ expect(found_schema['fields'][0]['name']).to eq('test_id')
14
+ new_schema = {
15
+ 'namespace' => 'com.my-namespace',
16
+ 'name' => 'MyNewSchema',
17
+ 'type' => 'record',
18
+ 'doc' => 'Test schema',
19
+ 'fields' => [
20
+ {
21
+ 'name' => 'my_id',
22
+ 'type' => 'int',
23
+ 'doc' => 'test int'
24
+ }
25
+ ]
26
+ }
27
+ schema_store.add_schema(new_schema)
28
+ found_schema = schema_store.find('MyNewSchema', 'com.my-namespace').
29
+ as_json
30
+ expect(found_schema['name']).to eq('MyNewSchema')
31
+ expect(found_schema['namespace']).to eq('com.my-namespace')
32
+ expect(found_schema['fields'].size).to eq(1)
33
+ expect(found_schema['fields'][0]['type']['type_sym']).to eq('int')
34
+ expect(found_schema['fields'][0]['name']).to eq('my_id')
35
+ end
36
+ end