logstash-output-elasticsearch 10.8.6-java → 11.0.3-java

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/docs/index.asciidoc +132 -22
  4. data/lib/logstash/outputs/elasticsearch.rb +125 -64
  5. data/lib/logstash/outputs/elasticsearch/data_stream_support.rb +233 -0
  6. data/lib/logstash/outputs/elasticsearch/http_client.rb +9 -7
  7. data/lib/logstash/outputs/elasticsearch/http_client/pool.rb +49 -62
  8. data/lib/logstash/outputs/elasticsearch/ilm.rb +13 -45
  9. data/lib/logstash/outputs/elasticsearch/license_checker.rb +26 -23
  10. data/lib/logstash/outputs/elasticsearch/template_manager.rb +4 -6
  11. data/lib/logstash/outputs/elasticsearch/templates/ecs-v1/elasticsearch-8x.json +1 -0
  12. data/lib/logstash/plugin_mixins/elasticsearch/api_configs.rb +157 -153
  13. data/lib/logstash/plugin_mixins/elasticsearch/common.rb +71 -58
  14. data/logstash-output-elasticsearch.gemspec +3 -3
  15. data/spec/es_spec_helper.rb +7 -12
  16. data/spec/fixtures/_nodes/{5x_6x.json → 6x.json} +5 -5
  17. data/spec/integration/outputs/compressed_indexing_spec.rb +47 -46
  18. data/spec/integration/outputs/data_stream_spec.rb +61 -0
  19. data/spec/integration/outputs/delete_spec.rb +49 -51
  20. data/spec/integration/outputs/ilm_spec.rb +236 -248
  21. data/spec/integration/outputs/index_spec.rb +5 -2
  22. data/spec/integration/outputs/index_version_spec.rb +78 -82
  23. data/spec/integration/outputs/ingest_pipeline_spec.rb +58 -58
  24. data/spec/integration/outputs/painless_update_spec.rb +74 -164
  25. data/spec/integration/outputs/parent_spec.rb +67 -75
  26. data/spec/integration/outputs/retry_spec.rb +6 -6
  27. data/spec/integration/outputs/sniffer_spec.rb +15 -54
  28. data/spec/integration/outputs/templates_spec.rb +79 -81
  29. data/spec/integration/outputs/update_spec.rb +99 -101
  30. data/spec/spec_helper.rb +10 -0
  31. data/spec/unit/outputs/elasticsearch/data_stream_support_spec.rb +528 -0
  32. data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +1 -0
  33. data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +36 -29
  34. data/spec/unit/outputs/elasticsearch/http_client_spec.rb +2 -3
  35. data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +10 -12
  36. data/spec/unit/outputs/elasticsearch_proxy_spec.rb +1 -2
  37. data/spec/unit/outputs/elasticsearch_spec.rb +176 -41
  38. data/spec/unit/outputs/elasticsearch_ssl_spec.rb +1 -2
  39. data/spec/unit/outputs/error_whitelist_spec.rb +3 -2
  40. data/spec/unit/outputs/license_check_spec.rb +0 -16
  41. metadata +29 -36
  42. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-2x.json +0 -95
  43. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-5x.json +0 -46
  44. data/spec/fixtures/_nodes/2x_1x.json +0 -27
  45. data/spec/fixtures/scripts/groovy/scripted_update.groovy +0 -2
  46. data/spec/fixtures/scripts/groovy/scripted_update_nested.groovy +0 -2
  47. data/spec/fixtures/scripts/groovy/scripted_upsert.groovy +0 -2
  48. data/spec/integration/outputs/groovy_update_spec.rb +0 -150
  49. data/spec/integration/outputs/templates_5x_spec.rb +0 -98
@@ -1,116 +1,114 @@
1
1
  require_relative "../../../spec/es_spec_helper"
2
2
 
3
- if ESHelper.es_version_satisfies?(">= 2")
4
- describe "Update actions without scripts", :integration => true do
5
- require "logstash/outputs/elasticsearch"
6
-
7
- def get_es_output( options={} )
8
- settings = {
9
- "manage_template" => true,
10
- "index" => "logstash-update",
11
- "template_overwrite" => true,
12
- "hosts" => get_host_port(),
13
- "action" => "update"
14
- }
15
- LogStash::Outputs::ElasticSearch.new(settings.merge!(options))
3
+ describe "Update actions without scripts", :integration => true do
4
+ require "logstash/outputs/elasticsearch"
5
+
6
+ def get_es_output( options={} )
7
+ settings = {
8
+ "manage_template" => true,
9
+ "index" => "logstash-update",
10
+ "template_overwrite" => true,
11
+ "hosts" => get_host_port(),
12
+ "action" => "update"
13
+ }
14
+ LogStash::Outputs::ElasticSearch.new(settings.merge!(options))
15
+ end
16
+
17
+ before :each do
18
+ @es = get_client
19
+ # Delete all templates first.
20
+ # Clean ES of data before we start.
21
+ @es.indices.delete_template(:name => "*")
22
+ # This can fail if there are no indexes, ignore failure.
23
+ @es.indices.delete(:index => "*") rescue nil
24
+ @es.index(
25
+ :index => 'logstash-update',
26
+ :type => doc_type,
27
+ :id => "123",
28
+ :body => { :message => 'Test', :counter => 1 }
29
+ )
30
+ @es.indices.refresh
31
+ end
32
+
33
+ it "should fail without a document_id" do
34
+ subject = get_es_output
35
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
36
+ end
37
+
38
+ context "when update only" do
39
+ it "should not create new document" do
40
+ subject = get_es_output({ 'document_id' => "456" } )
41
+ subject.register
42
+ subject.multi_receive([LogStash::Event.new("message" => "sample message here")])
43
+ expect {@es.get(:index => 'logstash-update', :type => doc_type, :id => "456", :refresh => true)}.to raise_error(Elasticsearch::Transport::Transport::Errors::NotFound)
44
+ end
45
+
46
+ it "should update existing document" do
47
+ subject = get_es_output({ 'document_id' => "123" })
48
+ subject.register
49
+ subject.multi_receive([LogStash::Event.new("message" => "updated message here")])
50
+ r = @es.get(:index => 'logstash-update', :type => doc_type, :id => "123", :refresh => true)
51
+ expect(r["_source"]["message"]).to eq('updated message here')
52
+ end
53
+
54
+ # The es ruby client treats the data field differently. Make sure this doesn't
55
+ # raise an exception
56
+ it "should update an existing document that has a 'data' field" do
57
+ subject = get_es_output({ 'document_id' => "123" })
58
+ subject.register
59
+ subject.multi_receive([LogStash::Event.new("data" => "updated message here", "message" => "foo")])
60
+ r = @es.get(:index => 'logstash-update', :type => doc_type, :id => "123", :refresh => true)
61
+ expect(r["_source"]["data"]).to eq('updated message here')
62
+ expect(r["_source"]["message"]).to eq('foo')
63
+ end
64
+
65
+ it "should allow default (internal) version" do
66
+ subject = get_es_output({ 'document_id' => "123", "version" => "99" })
67
+ subject.register
16
68
  end
17
69
 
18
- before :each do
19
- @es = get_client
20
- # Delete all templates first.
21
- # Clean ES of data before we start.
22
- @es.indices.delete_template(:name => "*")
23
- # This can fail if there are no indexes, ignore failure.
24
- @es.indices.delete(:index => "*") rescue nil
25
- @es.index(
26
- :index => 'logstash-update',
27
- :type => doc_type,
28
- :id => "123",
29
- :body => { :message => 'Test', :counter => 1 }
30
- )
31
- @es.indices.refresh
70
+ it "should allow internal version" do
71
+ subject = get_es_output({ 'document_id' => "123", "version" => "99", "version_type" => "internal" })
72
+ subject.register
32
73
  end
33
74
 
34
- it "should fail without a document_id" do
35
- subject = get_es_output
75
+ it "should not allow external version" do
76
+ subject = get_es_output({ 'document_id' => "123", "version" => "99", "version_type" => "external" })
36
77
  expect { subject.register }.to raise_error(LogStash::ConfigurationError)
37
78
  end
38
79
 
39
- context "when update only" do
40
- it "should not create new document" do
41
- subject = get_es_output({ 'document_id' => "456" } )
42
- subject.register
43
- subject.multi_receive([LogStash::Event.new("message" => "sample message here")])
44
- expect {@es.get(:index => 'logstash-update', :type => doc_type, :id => "456", :refresh => true)}.to raise_error(Elasticsearch::Transport::Transport::Errors::NotFound)
45
- end
46
-
47
- it "should update existing document" do
48
- subject = get_es_output({ 'document_id' => "123" })
49
- subject.register
50
- subject.multi_receive([LogStash::Event.new("message" => "updated message here")])
51
- r = @es.get(:index => 'logstash-update', :type => doc_type, :id => "123", :refresh => true)
52
- expect(r["_source"]["message"]).to eq('updated message here')
53
- end
54
-
55
- # The es ruby client treats the data field differently. Make sure this doesn't
56
- # raise an exception
57
- it "should update an existing document that has a 'data' field" do
58
- subject = get_es_output({ 'document_id' => "123" })
59
- subject.register
60
- subject.multi_receive([LogStash::Event.new("data" => "updated message here", "message" => "foo")])
61
- r = @es.get(:index => 'logstash-update', :type => doc_type, :id => "123", :refresh => true)
62
- expect(r["_source"]["data"]).to eq('updated message here')
63
- expect(r["_source"]["message"]).to eq('foo')
64
- end
65
-
66
- it "should allow default (internal) version" do
67
- subject = get_es_output({ 'document_id' => "123", "version" => "99" })
68
- subject.register
69
- end
70
-
71
- it "should allow internal version" do
72
- subject = get_es_output({ 'document_id' => "123", "version" => "99", "version_type" => "internal" })
73
- subject.register
74
- end
75
-
76
- it "should not allow external version" do
77
- subject = get_es_output({ 'document_id' => "123", "version" => "99", "version_type" => "external" })
78
- expect { subject.register }.to raise_error(LogStash::ConfigurationError)
79
- end
80
-
81
- it "should not allow external_gt version" do
82
- subject = get_es_output({ 'document_id' => "123", "version" => "99", "version_type" => "external_gt" })
83
- expect { subject.register }.to raise_error(LogStash::ConfigurationError)
84
- end
85
-
86
- it "should not allow external_gte version" do
87
- subject = get_es_output({ 'document_id' => "123", "version" => "99", "version_type" => "external_gte" })
88
- expect { subject.register }.to raise_error(LogStash::ConfigurationError)
89
- end
80
+ it "should not allow external_gt version" do
81
+ subject = get_es_output({ 'document_id' => "123", "version" => "99", "version_type" => "external_gt" })
82
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
83
+ end
90
84
 
85
+ it "should not allow external_gte version" do
86
+ subject = get_es_output({ 'document_id' => "123", "version" => "99", "version_type" => "external_gte" })
87
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
91
88
  end
92
89
 
93
- context "when update with upsert" do
94
- it "should create new documents with provided upsert" do
95
- subject = get_es_output({ 'document_id' => "456", 'upsert' => '{"message": "upsert message"}' })
96
- subject.register
97
- subject.multi_receive([LogStash::Event.new("message" => "sample message here")])
98
- r = @es.get(:index => 'logstash-update', :type => doc_type, :id => "456", :refresh => true)
99
- expect(r["_source"]["message"]).to eq('upsert message')
100
- end
101
-
102
- it "should create new documents with event/doc as upsert" do
103
- subject = get_es_output({ 'document_id' => "456", 'doc_as_upsert' => true })
104
- subject.register
105
- subject.multi_receive([LogStash::Event.new("message" => "sample message here")])
106
- r = @es.get(:index => 'logstash-update', :type => doc_type, :id => "456", :refresh => true)
107
- expect(r["_source"]["message"]).to eq('sample message here')
108
- end
109
-
110
- it "should fail on documents with event/doc as upsert at external version" do
111
- subject = get_es_output({ 'document_id' => "456", 'doc_as_upsert' => true, 'version' => 999, "version_type" => "external" })
112
- expect { subject.register }.to raise_error(LogStash::ConfigurationError)
113
- end
90
+ end
91
+
92
+ context "when update with upsert" do
93
+ it "should create new documents with provided upsert" do
94
+ subject = get_es_output({ 'document_id' => "456", 'upsert' => '{"message": "upsert message"}' })
95
+ subject.register
96
+ subject.multi_receive([LogStash::Event.new("message" => "sample message here")])
97
+ r = @es.get(:index => 'logstash-update', :type => doc_type, :id => "456", :refresh => true)
98
+ expect(r["_source"]["message"]).to eq('upsert message')
99
+ end
100
+
101
+ it "should create new documents with event/doc as upsert" do
102
+ subject = get_es_output({ 'document_id' => "456", 'doc_as_upsert' => true })
103
+ subject.register
104
+ subject.multi_receive([LogStash::Event.new("message" => "sample message here")])
105
+ r = @es.get(:index => 'logstash-update', :type => doc_type, :id => "456", :refresh => true)
106
+ expect(r["_source"]["message"]).to eq('sample message here')
107
+ end
108
+
109
+ it "should fail on documents with event/doc as upsert at external version" do
110
+ subject = get_es_output({ 'document_id' => "456", 'doc_as_upsert' => true, 'version' => 999, "version_type" => "external" })
111
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
114
112
  end
115
113
  end
116
114
  end
@@ -0,0 +1,10 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+
3
+ require "logstash/outputs/elasticsearch"
4
+
5
+ module LogStash::Outputs::ElasticSearch::SpecHelper
6
+ end
7
+
8
+ RSpec.configure do |config|
9
+ config.include LogStash::Outputs::ElasticSearch::SpecHelper
10
+ end
@@ -0,0 +1,528 @@
1
+ require_relative '../../../../spec/spec_helper'
2
+ require "logstash/outputs/elasticsearch/data_stream_support"
3
+
4
+ describe LogStash::Outputs::ElasticSearch::DataStreamSupport do
5
+
6
+ subject { LogStash::Outputs::ElasticSearch.new(options) }
7
+ let(:options) { { 'hosts' => [ 'localhost:12345' ] } }
8
+ let(:es_version) { '7.10.1' }
9
+
10
+ let(:do_register) { false }
11
+ let(:stub_plugin_register!) do
12
+ allow(subject).to receive(:last_es_version).and_return(es_version)
13
+
14
+ allow_any_instance_of(LogStash::Outputs::ElasticSearch::HttpClient::Pool).to receive(:start)
15
+
16
+ # stub-out unrelated (finish_register) setup:
17
+ allow(subject).to receive(:discover_cluster_uuid)
18
+ allow(subject).to receive(:install_template)
19
+ allow(subject).to receive(:ilm_in_use?).and_return nil
20
+
21
+ # emulate 'successful' ES connection on the same thread
22
+ allow(subject).to receive(:after_successful_connection) { |&block| block.call }
23
+ allow(subject).to receive(:stop_after_successful_connection_thread)
24
+
25
+ subject.register
26
+
27
+ allow(subject.client).to receive(:maximum_seen_major_version).and_return(Integer(es_version.split('.').first))
28
+
29
+ # allow( subject.logger ).to receive(:info) do |msg|
30
+ # expect(msg).to include "New Elasticsearch output"
31
+ # end
32
+ end
33
+
34
+ before(:each) do
35
+ stub_plugin_register! if do_register
36
+ end
37
+
38
+ after(:each) do
39
+ subject.close if do_register
40
+ end
41
+
42
+ context "default configuration" do
43
+
44
+ let(:options) { {} }
45
+
46
+ before { allow(subject).to receive(:last_es_version).and_return(es_version) }
47
+
48
+ it "does not use data-streams on LS 7.x" do
49
+ change_constant :LOGSTASH_VERSION, '7.10.0' do
50
+ expect( subject.data_stream_config? ).to be false
51
+ end
52
+ end
53
+
54
+ it "warns when configuration is data-stream compliant (LS 7.x)" do
55
+ expect( subject.logger ).to receive(:warn) do |msg|
56
+ expect(msg).to include "Configuration is data stream compliant but due backwards compatibility Logstash 7.x"
57
+ end
58
+ change_constant :LOGSTASH_VERSION, '7.11.0' do
59
+ expect( subject.data_stream_config? ).to be false
60
+ end
61
+ end
62
+
63
+ it "defaults to using data-streams on LS 8.0" do
64
+ change_constant :LOGSTASH_VERSION, '8.0.0' do
65
+ expect( subject.data_stream_config? ).to be true
66
+ end
67
+ end
68
+
69
+ context 'non-compatible ES' do
70
+
71
+ let(:es_version) { '7.8.0' }
72
+
73
+ it "does not print an error (from after_successful_connection thread)" do
74
+ change_constant :LOGSTASH_VERSION, '7.8.1' do
75
+ expect( subject.logger ).to_not receive(:error)
76
+ expect( subject ).to receive(:finish_register).once.and_call_original
77
+ stub_plugin_register!
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+
85
+ context "ds-compatible configuration" do
86
+
87
+ let(:options) do
88
+ {
89
+ 'hosts' => [ 'http://127.0.0.1:12345' ],
90
+ 'http_compression' => 'true', 'bulk_path' => '_bulk', 'timeout' => '30',
91
+ 'user' => 'elastic', 'password' => 'ForSearch!', 'ssl' => 'false'
92
+ }
93
+ end
94
+
95
+ before { allow(subject).to receive(:last_es_version).and_return(es_version) }
96
+
97
+ it "does not use data-streams on LS 7.x" do
98
+ change_constant :LOGSTASH_VERSION, '7.10.0' do
99
+ expect( subject.data_stream_config? ).to be false
100
+ end
101
+ end
102
+
103
+ it "defaults to using data-streams on LS 8.0" do
104
+ change_constant :LOGSTASH_VERSION, '8.0.1' do
105
+ expect( subject.data_stream_config? ).to be true
106
+ end
107
+ change_constant :LOGSTASH_VERSION, '8.1.0' do
108
+ expect( subject.send(:check_data_stream_config!) ).to be true
109
+ end
110
+ end
111
+
112
+ context 'old ES' do
113
+
114
+ let(:es_version) { '7.8.1' }
115
+
116
+ it "prints an error (from after_successful_connection thread) on LS 8.0" do
117
+ change_constant :LOGSTASH_VERSION, '8.0.0' do
118
+ expect( subject.logger ).to receive(:error).with(/Elasticsearch version does not support data streams/,
119
+ {:es_version=>"7.8.1"})
120
+ stub_plugin_register!
121
+ end
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+
128
+ context "default (non data-stream) configuration (on 7.x)" do
129
+
130
+ let(:options) do
131
+ { 'data_stream_dataset' => 'test', 'data_stream_auto_routing' => 'false', 'user' => 'elastic' }
132
+ end
133
+
134
+ before { allow(subject).to receive(:last_es_version).and_return(es_version) }
135
+
136
+ it "does not default to data-streams" do
137
+ expect( subject.logger ).to receive(:error) do |msg|
138
+ expect(msg).to include "Ambiguous configuration; data stream settings are present, but data streams are not enabled"
139
+ end
140
+ change_constant :LOGSTASH_VERSION, '7.10.2' do
141
+ expect { subject.data_stream_config? }.to raise_error(LogStash::ConfigurationError, /Ambiguous configuration/i)
142
+ end
143
+ end
144
+
145
+ context 'explicit data_stream => false' do
146
+
147
+ let(:options) { super().merge('data_stream' => 'false') }
148
+
149
+ it "raises a configuration error (due ds specific settings)" do
150
+ expect( subject.logger ).to receive(:error).with(/Ambiguous configuration; data stream settings must not be present when data streams is disabled/,
151
+ {"data_stream_auto_routing"=>"false", "data_stream_dataset"=>"test"})
152
+ change_constant :LOGSTASH_VERSION, '7.10.2' do
153
+ expect { subject.data_stream_config? }.to raise_error(LogStash::ConfigurationError, /Ambiguous configuration/i)
154
+ end
155
+ end
156
+
157
+ end
158
+
159
+ end
160
+
161
+ context "(explicit) ds disabled configuration" do
162
+
163
+ let(:options) { super().merge('data_stream' => false.to_s) }
164
+
165
+ before { allow(subject).to receive(:last_es_version).and_return(es_version) }
166
+
167
+ it "does not use data-streams on LS 7.x" do
168
+ change_constant :LOGSTASH_VERSION, '7.10.0' do
169
+ expect( subject.data_stream_config? ).to be false
170
+ end
171
+ end
172
+
173
+ it "does not use data-streams on LS 8.0" do
174
+ change_constant :LOGSTASH_VERSION, '8.0.0' do
175
+ expect( subject.data_stream_config? ).to be false
176
+ end
177
+ end
178
+
179
+ it "does not print a warning" do
180
+ expect( subject.logger ).to_not receive(:warn)
181
+ change_constant :LOGSTASH_VERSION, '7.10.2' do
182
+ expect( subject.data_stream_config? ).to be false
183
+ end
184
+ end
185
+
186
+ end
187
+
188
+ context "(explicit) ds enabled configuration" do
189
+
190
+ let(:options) { super().merge('data_stream' => true.to_s) }
191
+
192
+ before { allow(subject).to receive(:last_es_version).and_return(es_version) }
193
+
194
+ it "does use data-streams on LS 7.x" do
195
+ change_constant :LOGSTASH_VERSION, '7.9.1' do
196
+ expect( subject.data_stream_config? ).to be true
197
+ end
198
+ end
199
+
200
+ it "does use data-streams on LS 8.0" do
201
+ change_constant :LOGSTASH_VERSION, '8.1.0' do
202
+ expect( subject.data_stream_config? ).to be true
203
+ end
204
+ end
205
+
206
+ context 'non-compatible ES' do
207
+
208
+ let(:es_version) { '6.8.11' }
209
+
210
+ it "prints an error (from after_successful_connection thread) on LS 7.x" do
211
+ change_constant :LOGSTASH_VERSION, '7.12.0' do
212
+ expect( subject.logger ).to receive(:error).with(/Elasticsearch version does not support data streams/,
213
+ {:es_version=>"6.8.11"})
214
+ stub_plugin_register!
215
+ end
216
+ end
217
+
218
+ it "prints an error (from after_successful_connection thread) on LS 8.0" do
219
+ change_constant :LOGSTASH_VERSION, '8.0.5' do
220
+ expect( subject.logger ).to receive(:error).with(/Elasticsearch version does not support data streams/,
221
+ {:es_version=>"6.8.11"})
222
+ stub_plugin_register!
223
+ end
224
+ end
225
+
226
+ end
227
+
228
+ end
229
+
230
+ describe "auto routing" do
231
+
232
+ let(:options) { super().merge('data_stream' => 'true') }
233
+ let(:do_register) { true }
234
+
235
+ let(:event) do
236
+ event = LogStash::Event.new
237
+ event.set '[host][hostname]', 'orangutan'
238
+ event
239
+ end
240
+
241
+ context 'with data_stream.* event data' do
242
+
243
+ let(:event) do
244
+ super().tap do |event|
245
+ event.set '[data_stream][type]', 'metrics'
246
+ event.set '[data_stream][dataset]', 'src1'
247
+ event.set '[data_stream][namespace]', 'test'
248
+ end
249
+ end
250
+
251
+ it 'uses event specified target' do
252
+ tuple = subject.map_events([ event ]).first
253
+ expect( tuple.size ).to eql 3
254
+ expect( tuple[0] ).to eql 'create'
255
+ expect( tuple[1] ).to include :_index => 'metrics-src1-test'
256
+ end
257
+
258
+ end
259
+
260
+ context 'with routing turned off' do
261
+
262
+ let(:options) { super().merge('data_stream_auto_routing' => 'false') }
263
+
264
+ let(:event) do
265
+ super().tap do |event|
266
+ event.set '[data_stream][type]', 'metrics'
267
+ event.set '[data_stream][dataset]', 'src1'
268
+ event.set '[data_stream][namespace]', 'test'
269
+ end
270
+ end
271
+
272
+ it 'uses event specified target' do
273
+ tuple = subject.map_events([ event ]).first
274
+ expect( tuple.size ).to eql 3
275
+ expect( tuple[0] ).to eql 'create'
276
+ expect( tuple[1] ).to include :_index => 'logs-generic-default'
277
+ end
278
+
279
+ end
280
+
281
+ context 'with partial data_stream.* data' do
282
+
283
+ let(:options) { super().merge('data_stream_dataset' => 'data') }
284
+
285
+ let(:event) do
286
+ super().tap do |event|
287
+ event.set '[data_stream][type]', 'metrics'
288
+ event.set '[data_stream][dataset]', 'src1'
289
+ end
290
+ end
291
+
292
+ it 'uses event specified target' do
293
+ tuple = subject.map_events([ event ]).first
294
+ expect( tuple.size ).to eql 3
295
+ expect( tuple[0] ).to eql 'create'
296
+ expect( tuple[1] ).to include :_index => 'metrics-src1-default'
297
+ end
298
+
299
+ end
300
+
301
+ context 'with no data_stream.* fields' do
302
+
303
+ let(:options) { super().merge('data_stream_dataset' => 'stats', 'data_stream_type' => 'metrics') }
304
+
305
+ it 'uses configuration target' do
306
+ tuple = subject.map_events([ event ]).first
307
+ expect( tuple.size ).to eql 3
308
+ expect( tuple[0] ).to eql 'create'
309
+ expect( tuple[1] ).to include :_index => 'metrics-stats-default'
310
+ end
311
+
312
+ end
313
+
314
+ context 'with default configuration' do
315
+
316
+ it 'uses default target' do
317
+ tuple = subject.map_events([ event ]).first
318
+ expect( tuple.size ).to eql 3
319
+ expect( tuple[0] ).to eql 'create'
320
+ expect( tuple[1] ).to include :_index => 'logs-generic-default'
321
+ end
322
+
323
+ end
324
+
325
+ end
326
+
327
+ describe "field sync" do
328
+
329
+ let(:options) { super().merge('data_stream' => 'true') }
330
+
331
+ let(:do_register) { true }
332
+
333
+ let(:event) do
334
+ event = LogStash::Event.new
335
+ event.set '[host][hostname]', 'orangutan'
336
+ event
337
+ end
338
+
339
+ context 'enabled and no event data' do
340
+
341
+ let(:options) { super().merge('data_stream_sync_fields' => 'true') }
342
+
343
+ it 'fills in DS fields' do
344
+ tuple = subject.map_events([ event ]).first
345
+ expect( tuple.size ).to eql 3
346
+ expect( tuple[2]['data_stream'] ).to eql({"type" => "logs", "dataset" => "generic", "namespace" => "default"})
347
+ end
348
+
349
+ end
350
+
351
+ context 'enabled and some event data' do
352
+
353
+ let(:options) { super().merge('data_stream_dataset' => 'ds1', 'data_stream_sync_fields' => 'true') }
354
+
355
+ let(:event) do
356
+ super().tap do |event|
357
+ event.set '[data_stream][namespace]', 'custom'
358
+ end
359
+ end
360
+
361
+ it 'fills in missing fields' do
362
+ tuple = subject.map_events([ event ]).first
363
+ expect( tuple.size ).to eql 3
364
+ expect( tuple[2]['data_stream'] ).to eql({"type" => "logs", "dataset" => "ds1", "namespace" => "custom"})
365
+ end
366
+
367
+ it 'does not mutate data_stream hash' do
368
+ data_stream = event.get('data_stream')
369
+ data_stream_dup = data_stream.dup
370
+ subject.map_events([ event ])
371
+ expect( data_stream ).to eql data_stream_dup
372
+ end
373
+
374
+ end
375
+
376
+ context 'enabled with invalid data' do
377
+
378
+ let(:options) { super().merge('data_stream_sync_fields' => 'true') }
379
+
380
+ let(:event) do
381
+ super().tap do |event|
382
+ event.set '[data_stream]', false
383
+ end
384
+ end
385
+
386
+ it 'overwrites invalid data_stream field' do
387
+ tuple = subject.map_events([ event ]).first
388
+ expect( tuple.size ).to eql 3
389
+ expect( tuple[2]['data_stream'] ).to eql({"type" => "logs", "dataset" => "generic", "namespace" => "default"})
390
+ end
391
+
392
+ end
393
+
394
+ context 'enabled having invalid data with routing disabled' do
395
+
396
+ let(:options) do
397
+ super().merge('data_stream_sync_fields' => 'true', 'data_stream_auto_routing' => 'false', 'data_stream_namespace' => 'ns1')
398
+ end
399
+
400
+ let(:event) do
401
+ super().tap do |event|
402
+ event.set '[data_stream][type]', 'foo'
403
+ event.set '[data_stream][dataset]', false
404
+ event.set '[data_stream][extra]', 0
405
+ end
406
+ end
407
+
408
+ it 'overwrites invalid data_stream sub-fields' do
409
+ tuple = subject.map_events([ event ]).first
410
+ expect( tuple.size ).to eql 3
411
+ expect( tuple[2]['data_stream'] ).to eql({"type" => "logs", "dataset" => "generic", "namespace" => "ns1", "extra" => 0})
412
+ end
413
+
414
+ end
415
+
416
+ context 'disabled and no event data' do
417
+
418
+ let(:options) { super().merge('data_stream_dataset' => 'ds1', 'data_stream_sync_fields' => 'false') }
419
+
420
+ it 'does not fill DS fields' do
421
+ tuple = subject.map_events([ event ]).first
422
+ expect( tuple.size ).to eql 3
423
+ expect( tuple[2].keys ).to_not include 'data_stream'
424
+ end
425
+
426
+ end
427
+
428
+ context 'disabled and some event data' do
429
+
430
+ let(:options) { super().merge('data_stream_sync_fields' => 'false') }
431
+
432
+ let(:event) do
433
+ super().tap do |event|
434
+ event.set '[data_stream][type]', 'logs'
435
+ end
436
+ end
437
+
438
+ it 'does not fill DS fields' do
439
+ tuple = subject.map_events([ event ]).first
440
+ expect( tuple.size ).to eql 3
441
+ expect( tuple[2]['data_stream'] ).to eql({ 'type' => 'logs'})
442
+ end
443
+
444
+ end
445
+
446
+ end
447
+
448
+ describe "validation" do
449
+
450
+ context 'with too long dataset name' do
451
+
452
+ let(:options) { super().merge('data_stream' => 'true', 'data_stream_dataset' => 'x' * 120) }
453
+
454
+ it 'fails' do
455
+ expect { LogStash::Outputs::ElasticSearch.new(options) }.to raise_error LogStash::ConfigurationError
456
+ end
457
+
458
+ end
459
+
460
+ context 'with empty dataset name' do
461
+
462
+ let(:options) { super().merge('data_stream' => 'true', 'data_stream_dataset' => '') }
463
+
464
+ it 'fails' do
465
+ expect { LogStash::Outputs::ElasticSearch.new(options) }.to raise_error LogStash::ConfigurationError
466
+ end
467
+
468
+ end
469
+
470
+ context 'with invalid dataset char' do
471
+
472
+ let(:options) { super().merge('data_stream' => 'true', 'data_stream_dataset' => 'foo/bar') }
473
+
474
+ it 'fails' do
475
+ expect { LogStash::Outputs::ElasticSearch.new(options) }.to raise_error LogStash::ConfigurationError
476
+ end
477
+
478
+ end
479
+
480
+ context 'with invalid namespace char' do
481
+
482
+ let(:options) { super().merge('data_stream' => 'true', 'data_stream_namespace' => 'foo*') }
483
+
484
+ it 'fails' do
485
+ expect { LogStash::Outputs::ElasticSearch.new(options) }.to raise_error LogStash::ConfigurationError
486
+ end
487
+
488
+ end
489
+
490
+ context 'with invalid "empty" namespace' do
491
+
492
+ let(:options) { super().merge('data_stream' => 'true', 'data_stream_namespace' => ' ') }
493
+
494
+ it 'fails' do
495
+ expect { LogStash::Outputs::ElasticSearch.new(options) }.to raise_error LogStash::ConfigurationError
496
+ end
497
+
498
+ end
499
+
500
+ context 'with invalid type' do
501
+
502
+ let(:options) { super().merge('data_stream' => 'true', 'data_stream_type' => 'custom') }
503
+
504
+ it 'fails' do
505
+ expect { LogStash::Outputs::ElasticSearch.new(options) }.to raise_error LogStash::ConfigurationError
506
+ end
507
+
508
+ end
509
+
510
+ end
511
+
512
+ private
513
+
514
+ def change_constant(name, new_value, target: Object)
515
+ old_value = target.const_get name
516
+ begin
517
+ target.send :remove_const, name
518
+ target.const_set name, new_value
519
+ yield if block_given?
520
+ ensure
521
+ if block_given?
522
+ target.send :remove_const, name rescue nil
523
+ target.const_set name, old_value
524
+ end
525
+ end
526
+ end
527
+
528
+ end