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

Sign up to get free protection for your applications and to get access to all the features.
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