logstash-output-opensearch 1.0.0-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 (61) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ADMINS.md +29 -0
  5. data/CODE_OF_CONDUCT.md +25 -0
  6. data/CONTRIBUTING.md +99 -0
  7. data/DEVELOPER_GUIDE.md +208 -0
  8. data/Gemfile +20 -0
  9. data/LICENSE +202 -0
  10. data/MAINTAINERS.md +71 -0
  11. data/NOTICE +2 -0
  12. data/README.md +37 -0
  13. data/RELEASING.md +36 -0
  14. data/SECURITY.md +3 -0
  15. data/lib/logstash/outputs/opensearch.rb +449 -0
  16. data/lib/logstash/outputs/opensearch/distribution_checker.rb +44 -0
  17. data/lib/logstash/outputs/opensearch/http_client.rb +465 -0
  18. data/lib/logstash/outputs/opensearch/http_client/manticore_adapter.rb +140 -0
  19. data/lib/logstash/outputs/opensearch/http_client/pool.rb +467 -0
  20. data/lib/logstash/outputs/opensearch/http_client_builder.rb +182 -0
  21. data/lib/logstash/outputs/opensearch/template_manager.rb +60 -0
  22. data/lib/logstash/outputs/opensearch/templates/ecs-disabled/1x.json +44 -0
  23. data/lib/logstash/outputs/opensearch/templates/ecs-disabled/7x.json +44 -0
  24. data/lib/logstash/plugin_mixins/opensearch/api_configs.rb +168 -0
  25. data/lib/logstash/plugin_mixins/opensearch/common.rb +294 -0
  26. data/lib/logstash/plugin_mixins/opensearch/noop_distribution_checker.rb +18 -0
  27. data/logstash-output-opensearch.gemspec +40 -0
  28. data/spec/fixtures/_nodes/nodes.json +74 -0
  29. data/spec/fixtures/htpasswd +2 -0
  30. data/spec/fixtures/nginx_reverse_proxy.conf +22 -0
  31. data/spec/fixtures/scripts/painless/scripted_update.painless +2 -0
  32. data/spec/fixtures/scripts/painless/scripted_update_nested.painless +1 -0
  33. data/spec/fixtures/scripts/painless/scripted_upsert.painless +1 -0
  34. data/spec/integration/outputs/compressed_indexing_spec.rb +76 -0
  35. data/spec/integration/outputs/create_spec.rb +76 -0
  36. data/spec/integration/outputs/delete_spec.rb +72 -0
  37. data/spec/integration/outputs/index_spec.rb +164 -0
  38. data/spec/integration/outputs/index_version_spec.rb +110 -0
  39. data/spec/integration/outputs/ingest_pipeline_spec.rb +82 -0
  40. data/spec/integration/outputs/metrics_spec.rb +75 -0
  41. data/spec/integration/outputs/no_opensearch_on_startup_spec.rb +67 -0
  42. data/spec/integration/outputs/painless_update_spec.rb +147 -0
  43. data/spec/integration/outputs/parent_spec.rb +103 -0
  44. data/spec/integration/outputs/retry_spec.rb +182 -0
  45. data/spec/integration/outputs/routing_spec.rb +70 -0
  46. data/spec/integration/outputs/sniffer_spec.rb +70 -0
  47. data/spec/integration/outputs/templates_spec.rb +105 -0
  48. data/spec/integration/outputs/update_spec.rb +123 -0
  49. data/spec/opensearch_spec_helper.rb +141 -0
  50. data/spec/spec_helper.rb +19 -0
  51. data/spec/unit/http_client_builder_spec.rb +194 -0
  52. data/spec/unit/outputs/error_whitelist_spec.rb +62 -0
  53. data/spec/unit/outputs/opensearch/http_client/manticore_adapter_spec.rb +159 -0
  54. data/spec/unit/outputs/opensearch/http_client/pool_spec.rb +306 -0
  55. data/spec/unit/outputs/opensearch/http_client_spec.rb +292 -0
  56. data/spec/unit/outputs/opensearch/template_manager_spec.rb +36 -0
  57. data/spec/unit/outputs/opensearch_proxy_spec.rb +112 -0
  58. data/spec/unit/outputs/opensearch_spec.rb +800 -0
  59. data/spec/unit/outputs/opensearch_ssl_spec.rb +179 -0
  60. metadata +289 -0
  61. metadata.gz.sig +0 -0
@@ -0,0 +1,2 @@
1
+ ctx._source.counter += params.event.count
2
+
@@ -0,0 +1 @@
1
+ ctx._source.counter += params.event.data.count
@@ -0,0 +1 @@
1
+ ctx._source.counter = params.event.counter
@@ -0,0 +1,76 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+
10
+ require_relative "../../../spec/opensearch_spec_helper"
11
+ require "logstash/outputs/opensearch"
12
+ require "stringio"
13
+
14
+ RSpec::Matchers.define :a_valid_gzip_encoded_string do
15
+ match { |data|
16
+ expect { Zlib::GzipReader.new(StringIO.new(data)).read }.not_to raise_error
17
+ }
18
+ end
19
+
20
+
21
+ describe "indexing with http_compression turned on", :integration => true do
22
+ let(:event) { LogStash::Event.new("message" => "Hello World!", "type" => type) }
23
+ let(:index) { 10.times.collect { rand(10).to_s }.join("") }
24
+ let(:type) { "_doc" }
25
+ let(:event_count) { 10000 + rand(500) }
26
+ let(:events) { event_count.times.map { event }.to_a }
27
+ let(:config) {
28
+ {
29
+ "hosts" => get_host_port,
30
+ "index" => index,
31
+ "http_compression" => true
32
+ }
33
+ }
34
+ subject { LogStash::Outputs::OpenSearch.new(config) }
35
+
36
+ let(:es_url) { "http://#{get_host_port}" }
37
+ let(:index_url) {"#{es_url}/#{index}"}
38
+ let(:http_client_options) { {} }
39
+ let(:http_client) do
40
+ Manticore::Client.new(http_client_options)
41
+ end
42
+
43
+ before do
44
+ subject.register
45
+ subject.multi_receive([])
46
+ end
47
+
48
+ shared_examples "an indexer" do
49
+ it "ships events" do
50
+ subject.multi_receive(events)
51
+
52
+ http_client.post("#{es_url}/_refresh").call
53
+
54
+ response = http_client.get("#{index_url}/_count?q=*")
55
+ result = LogStash::Json.load(response.body)
56
+ cur_count = result["count"]
57
+ expect(cur_count).to eq(event_count)
58
+
59
+ response = http_client.get("#{index_url}/_search?q=*&size=1000")
60
+ result = LogStash::Json.load(response.body)
61
+ result["hits"]["hits"].each do |doc|
62
+ expect(doc["_type"]).to eq(type)
63
+ expect(doc["_index"]).to eq(index)
64
+ end
65
+ end
66
+ end
67
+
68
+ it "sets the correct content-encoding header and body is compressed" do
69
+ expect(subject.client.pool.adapter.client).to receive(:send).
70
+ with(anything, anything, {:headers=>{"Content-Encoding"=>"gzip", "Content-Type"=>"application/json"}, :body => a_valid_gzip_encoded_string}).
71
+ and_call_original
72
+ subject.multi_receive(events)
73
+ end
74
+
75
+ it_behaves_like("an indexer")
76
+ end
@@ -0,0 +1,76 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+
10
+ require_relative "../../../spec/opensearch_spec_helper"
11
+
12
+ describe "client create actions", :integration => true do
13
+ require "logstash/outputs/opensearch"
14
+
15
+ def get_es_output(action, id, version=nil, version_type=nil)
16
+ settings = {
17
+ "manage_template" => true,
18
+ "index" => "logstash-create",
19
+ "template_overwrite" => true,
20
+ "hosts" => get_host_port(),
21
+ "action" => action
22
+ }
23
+ settings['document_id'] = id
24
+ settings['version'] = version if version
25
+ settings['version_type'] = version_type if version_type
26
+ LogStash::Outputs::OpenSearch.new(settings)
27
+ end
28
+
29
+ before :each do
30
+ @client = get_client
31
+ # Delete all templates first.
32
+ # Clean OpenSearch of data before we start.
33
+ @client.indices.delete_template(:name => "*")
34
+ # This can fail if there are no indexes, ignore failure.
35
+ @client.indices.delete(:index => "*") rescue nil
36
+ end
37
+
38
+ context "when action => create" do
39
+ it "should create new documents with or without id" do
40
+ subject = get_es_output("create", "id123")
41
+ subject.register
42
+ subject.multi_receive([LogStash::Event.new("message" => "sample message here")])
43
+ @client.indices.refresh
44
+ # Wait or fail until everything's indexed.
45
+ Stud::try(3.times) do
46
+ r = @client.search(index: 'logstash-*')
47
+ expect(r).to have_hits(1)
48
+ end
49
+ end
50
+
51
+ it "should allow default (internal) version" do
52
+ subject = get_es_output("create", "id123", 43)
53
+ subject.register
54
+ end
55
+
56
+ it "should allow internal version" do
57
+ subject = get_es_output("create", "id123", 43, "internal")
58
+ subject.register
59
+ end
60
+
61
+ it "should not allow external version" do
62
+ subject = get_es_output("create", "id123", 43, "external")
63
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
64
+ end
65
+
66
+ it "should not allow external_gt version" do
67
+ subject = get_es_output("create", "id123", 43, "external_gt")
68
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
69
+ end
70
+
71
+ it "should not allow external_gte version" do
72
+ subject = get_es_output("create", "id123", 43, "external_gte")
73
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,72 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+
10
+ require_relative "../../../spec/opensearch_spec_helper"
11
+ require "logstash/outputs/opensearch"
12
+
13
+
14
+ describe "Versioned delete", :integration => true do
15
+ require "logstash/outputs/opensearch"
16
+
17
+ let(:es) { get_client }
18
+
19
+ before :each do
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.indices.refresh
26
+ end
27
+
28
+ context "when delete only" do
29
+ subject { LogStash::Outputs::OpenSearch.new(settings) }
30
+
31
+ before do
32
+ subject.register
33
+ end
34
+
35
+ let(:settings) do
36
+ {
37
+ "manage_template" => true,
38
+ "index" => "logstash-delete",
39
+ "template_overwrite" => true,
40
+ "hosts" => get_host_port(),
41
+ "document_id" => "%{my_id}",
42
+ "version" => "%{my_version}",
43
+ "version_type" => "external",
44
+ "action" => "%{my_action}"
45
+ }
46
+ end
47
+
48
+ it "should ignore non-monotonic external version updates" do
49
+ id = "ev2"
50
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_action" => "index", "message" => "foo", "my_version" => 99)])
51
+ r = es.get(:index => 'logstash-delete', :type => doc_type, :id => id, :refresh => true)
52
+ expect(r['_version']).to eq(99)
53
+ expect(r['_source']['message']).to eq('foo')
54
+
55
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_action" => "delete", "message" => "foo", "my_version" => 98)])
56
+ r2 = es.get(:index => 'logstash-delete', :type => doc_type, :id => id, :refresh => true)
57
+ expect(r2['_version']).to eq(99)
58
+ expect(r2['_source']['message']).to eq('foo')
59
+ end
60
+
61
+ it "should commit monotonic external version updates" do
62
+ id = "ev3"
63
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_action" => "index", "message" => "foo", "my_version" => 99)])
64
+ r = es.get(:index => 'logstash-delete', :type => doc_type, :id => id, :refresh => true)
65
+ expect(r['_version']).to eq(99)
66
+ expect(r['_source']['message']).to eq('foo')
67
+
68
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_action" => "delete", "message" => "foo", "my_version" => 100)])
69
+ expect { es.get(:index => 'logstash-delete', :type => doc_type, :id => id, :refresh => true) }.to raise_error(Elasticsearch::Transport::Transport::Errors::NotFound)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,164 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+
10
+ require_relative "../../../spec/opensearch_spec_helper"
11
+ require "logstash/outputs/opensearch"
12
+
13
+ describe "TARGET_BULK_BYTES", :integration => true do
14
+ let(:target_bulk_bytes) { LogStash::Outputs::OpenSearch::TARGET_BULK_BYTES }
15
+ let(:event_count) { 1000 }
16
+ let(:events) { event_count.times.map { event }.to_a }
17
+ let(:config) {
18
+ {
19
+ "hosts" => get_host_port,
20
+ "index" => index
21
+ }
22
+ }
23
+ let(:index) { 10.times.collect { rand(10).to_s }.join("") }
24
+ let(:type) { "_doc" }
25
+
26
+ subject { LogStash::Outputs::OpenSearch.new(config) }
27
+
28
+ before do
29
+ subject.register
30
+ allow(subject.client).to receive(:bulk_send).with(any_args).and_call_original
31
+ subject.multi_receive(events)
32
+ end
33
+
34
+ describe "batches that are too large for one" do
35
+ let(:event) { LogStash::Event.new("message" => "a " * (((target_bulk_bytes/2) / event_count)+1)) }
36
+
37
+ it "should send in two batches" do
38
+ expect(subject.client).to have_received(:bulk_send).twice do |payload|
39
+ expect(payload.size).to be <= target_bulk_bytes
40
+ end
41
+ end
42
+
43
+ describe "batches that fit in one" do
44
+ # Normally you'd want to generate a request that's just 1 byte below the limit, but it's
45
+ # impossible to know how many bytes an event will serialize as with bulk proto overhead
46
+ let(:event) { LogStash::Event.new("message" => "a") }
47
+
48
+ it "should send in one batch" do
49
+ expect(subject.client).to have_received(:bulk_send).once do |payload|
50
+ expect(payload.size).to be <= target_bulk_bytes
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ describe "indexing" do
58
+ let(:event) { LogStash::Event.new("message" => "Hello World!", "type" => type) }
59
+ let(:index) { 10.times.collect { rand(10).to_s }.join("") }
60
+ let(:type) { "_doc" }
61
+ let(:event_count) { 1 + rand(2) }
62
+ let(:config) { "not implemented" }
63
+ let(:events) { event_count.times.map { event }.to_a }
64
+ subject { LogStash::Outputs::OpenSearch.new(config) }
65
+
66
+ let(:es_url) { "http://#{get_host_port}" }
67
+ let(:index_url) {"#{es_url}/#{index}"}
68
+ let(:http_client_options) { {} }
69
+ let(:http_client) do
70
+ Manticore::Client.new(http_client_options)
71
+ end
72
+
73
+ before do
74
+ subject.register
75
+ subject.multi_receive([])
76
+ end
77
+
78
+ shared_examples "an indexer" do |secure|
79
+ it "ships events" do
80
+ subject.multi_receive(events)
81
+
82
+ http_client.post("#{es_url}/_refresh").call
83
+
84
+ response = http_client.get("#{index_url}/_count?q=*")
85
+ result = LogStash::Json.load(response.body)
86
+ cur_count = result["count"]
87
+ expect(cur_count).to eq(event_count)
88
+
89
+ response = http_client.get("#{index_url}/_search?q=*&size=1000")
90
+ result = LogStash::Json.load(response.body)
91
+ result["hits"]["hits"].each do |doc|
92
+ expect(doc["_type"]).to eq(type)
93
+ expect(doc["_index"]).to eq(index)
94
+ end
95
+ end
96
+
97
+ it "sets the correct content-type header" do
98
+ expected_manticore_opts = {:headers => {"Content-Type" => "application/json"}, :body => anything}
99
+ if secure
100
+ expected_manticore_opts = {
101
+ :headers => {"Content-Type" => "application/json"},
102
+ :body => anything,
103
+ :auth => {
104
+ :user => user,
105
+ :password => password,
106
+ :eager => true
107
+ }}
108
+ end
109
+ expect(subject.client.pool.adapter.client).to receive(:send).
110
+ with(anything, anything, expected_manticore_opts).at_least(:once).
111
+ and_call_original
112
+ subject.multi_receive(events)
113
+ end
114
+ end
115
+
116
+ describe "an indexer with custom index_type", :integration => true do
117
+ let(:config) {
118
+ {
119
+ "hosts" => get_host_port,
120
+ "index" => index
121
+ }
122
+ }
123
+ it_behaves_like("an indexer")
124
+ end
125
+
126
+ describe "an indexer with no type value set (default to doc)", :integration => true do
127
+ let(:type) { "_doc" }
128
+ let(:config) {
129
+ {
130
+ "hosts" => get_host_port,
131
+ "index" => index
132
+ }
133
+ }
134
+ it_behaves_like("an indexer")
135
+ end
136
+ describe "a secured indexer", :secure_integration => true do
137
+ let(:user) { "admin" }
138
+ let(:password) { "admin" }
139
+ let(:es_url) {"https://integration:9200"}
140
+ let(:config) do
141
+ {
142
+ "hosts" => ["integration:9200"],
143
+ "user" => user,
144
+ "password" => password,
145
+ "ssl" => true,
146
+ "ssl_certificate_verification" => false,
147
+ "index" => index
148
+ }
149
+ end
150
+ let(:http_client_options) do
151
+ {
152
+ :auth => {
153
+ :user => user,
154
+ :password => password
155
+ },
156
+ :ssl => {
157
+ :enabled => true,
158
+ :verify => false
159
+ }
160
+ }
161
+ end
162
+ it_behaves_like("an indexer", true)
163
+ end
164
+ end
@@ -0,0 +1,110 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+
10
+ require_relative "../../../spec/opensearch_spec_helper"
11
+ require "logstash/outputs/opensearch"
12
+
13
+ describe "Versioned indexing", :integration => true do
14
+ require "logstash/outputs/opensearch"
15
+
16
+ let(:es) { get_client }
17
+
18
+ before :each do
19
+ # Delete all templates first.
20
+ # Clean OpenSearch 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.indices.refresh
25
+ end
26
+
27
+ context "when index only" do
28
+ subject { LogStash::Outputs::OpenSearch.new(settings) }
29
+
30
+ before do
31
+ subject.register
32
+ end
33
+
34
+ describe "unversioned output" do
35
+ let(:settings) do
36
+ {
37
+ "manage_template" => true,
38
+ "index" => "logstash-index",
39
+ "template_overwrite" => true,
40
+ "hosts" => get_host_port(),
41
+ "action" => "index",
42
+ "script_lang" => "groovy",
43
+ "document_id" => "%{my_id}"
44
+ }
45
+ end
46
+
47
+ it "should default to OpenSearch version" do
48
+ subject.multi_receive([LogStash::Event.new("my_id" => "123", "message" => "foo")])
49
+ r = es.get(:index => 'logstash-index', :type => doc_type, :id => "123", :refresh => true)
50
+ expect(r["_version"]).to eq(1)
51
+ expect(r["_source"]["message"]).to eq('foo')
52
+ subject.multi_receive([LogStash::Event.new("my_id" => "123", "message" => "foobar")])
53
+ r2 = es.get(:index => 'logstash-index', :type => doc_type, :id => "123", :refresh => true)
54
+ expect(r2["_version"]).to eq(2)
55
+ expect(r2["_source"]["message"]).to eq('foobar')
56
+ end
57
+ end
58
+
59
+ describe "versioned output" do
60
+ let(:settings) do
61
+ {
62
+ "manage_template" => true,
63
+ "index" => "logstash-index",
64
+ "template_overwrite" => true,
65
+ "hosts" => get_host_port(),
66
+ "action" => "index",
67
+ "script_lang" => "groovy",
68
+ "document_id" => "%{my_id}",
69
+ "version" => "%{my_version}",
70
+ "version_type" => "external",
71
+ }
72
+ end
73
+
74
+ it "should respect the external version" do
75
+ id = "ev1"
76
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "99", "message" => "foo")])
77
+ r = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
78
+ expect(r["_version"]).to eq(99)
79
+ expect(r["_source"]["message"]).to eq('foo')
80
+ end
81
+
82
+ it "should ignore non-monotonic external version updates" do
83
+ id = "ev2"
84
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "99", "message" => "foo")])
85
+ r = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
86
+ expect(r["_version"]).to eq(99)
87
+ expect(r["_source"]["message"]).to eq('foo')
88
+
89
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "98", "message" => "foo")])
90
+ r2 = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
91
+ expect(r2["_version"]).to eq(99)
92
+ expect(r2["_source"]["message"]).to eq('foo')
93
+ end
94
+
95
+ it "should commit monotonic external version updates" do
96
+ id = "ev3"
97
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "99", "message" => "foo")])
98
+ r = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
99
+ expect(r["_version"]).to eq(99)
100
+ expect(r["_source"]["message"]).to eq('foo')
101
+
102
+ subject.multi_receive([LogStash::Event.new("my_id" => id, "my_version" => "100", "message" => "foo")])
103
+ r2 = es.get(:index => 'logstash-index', :type => doc_type, :id => id, :refresh => true)
104
+ expect(r2["_version"]).to eq(100)
105
+ expect(r2["_source"]["message"]).to eq('foo')
106
+ end
107
+ end
108
+ end
109
+ end
110
+