logstash-output-opensearch 1.0.0-java

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