logstash-output-amazon_es 2.0.1-java → 6.4.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 (35) hide show
  1. checksums.yaml +5 -5
  2. data/CONTRIBUTORS +12 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE +10 -199
  5. data/README.md +34 -65
  6. data/lib/logstash/outputs/amazon_es.rb +218 -423
  7. data/lib/logstash/outputs/amazon_es/common.rb +347 -0
  8. data/lib/logstash/outputs/amazon_es/common_configs.rb +141 -0
  9. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es2x.json +95 -0
  10. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es5x.json +46 -0
  11. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es6x.json +45 -0
  12. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es7x.json +46 -0
  13. data/lib/logstash/outputs/amazon_es/http_client.rb +359 -74
  14. data/lib/logstash/outputs/amazon_es/http_client/manticore_adapter.rb +169 -0
  15. data/lib/logstash/outputs/amazon_es/http_client/pool.rb +457 -0
  16. data/lib/logstash/outputs/amazon_es/http_client_builder.rb +164 -0
  17. data/lib/logstash/outputs/amazon_es/template_manager.rb +36 -0
  18. data/logstash-output-amazon_es.gemspec +13 -22
  19. data/spec/es_spec_helper.rb +37 -0
  20. data/spec/unit/http_client_builder_spec.rb +189 -0
  21. data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +105 -0
  22. data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +198 -0
  23. data/spec/unit/outputs/elasticsearch/http_client_spec.rb +222 -0
  24. data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +25 -0
  25. data/spec/unit/outputs/elasticsearch_spec.rb +615 -0
  26. data/spec/unit/outputs/error_whitelist_spec.rb +60 -0
  27. metadata +49 -110
  28. data/lib/logstash/outputs/amazon_es/aws_transport.rb +0 -109
  29. data/lib/logstash/outputs/amazon_es/aws_v4_signer.rb +0 -7
  30. data/lib/logstash/outputs/amazon_es/aws_v4_signer_impl.rb +0 -62
  31. data/lib/logstash/outputs/amazon_es/elasticsearch-template.json +0 -41
  32. data/spec/amazon_es_spec_helper.rb +0 -69
  33. data/spec/unit/outputs/amazon_es_spec.rb +0 -50
  34. data/spec/unit/outputs/elasticsearch/protocol_spec.rb +0 -36
  35. data/spec/unit/outputs/elasticsearch_proxy_spec.rb +0 -58
@@ -0,0 +1,105 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/outputs/amazon_es/http_client"
3
+
4
+ describe LogStash::Outputs::ElasticSearch::HttpClient::ManticoreAdapter do
5
+ let(:logger) { Cabin::Channel.get }
6
+ let(:options) { {:aws_access_key_id => 'AAAAAAAAAAAAAAAAAAAA',
7
+ :aws_secret_access_key => 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'} }
8
+
9
+ subject { described_class.new(logger, options) }
10
+
11
+ it "should raise an exception if requests are issued after close" do
12
+ subject.close
13
+ expect { subject.perform_request(::LogStash::Util::SafeURI.new("http://localhost:9200"), :get, '/') }.to raise_error(::Manticore::ClientStoppedException)
14
+ end
15
+
16
+ it "should implement host unreachable exceptions" do
17
+ expect(subject.host_unreachable_exceptions).to be_a(Array)
18
+ end
19
+
20
+
21
+ describe "bad response codes" do
22
+ let(:uri) { ::LogStash::Util::SafeURI.new("http://localhost:9200") }
23
+
24
+ it "should raise a bad response code error" do
25
+ resp = double("response")
26
+ allow(resp).to receive(:call)
27
+ allow(resp).to receive(:code).and_return(500)
28
+ allow(resp).to receive(:body).and_return("a body")
29
+
30
+ expect(subject.manticore).to receive(:get).
31
+ with(uri.to_s + "/", anything).
32
+ and_return(resp)
33
+
34
+ uri_with_path = uri.clone
35
+ uri_with_path.path = "/"
36
+
37
+ expect(::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError).to receive(:new).
38
+ with(resp.code, uri_with_path, nil, resp.body).and_call_original
39
+
40
+ expect do
41
+ subject.perform_request(uri, :get, "/")
42
+ end.to raise_error(::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError)
43
+ end
44
+ end
45
+
46
+ describe "format_url" do
47
+ let(:url) { ::LogStash::Util::SafeURI.new("http://localhost:9200/path/") }
48
+ let(:path) { "_bulk" }
49
+ subject { described_class.new(double("logger"),
50
+ {:aws_access_key_id => 'AAAAAAAAAAAAAAAAAAAA',
51
+ :aws_secret_access_key => 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'} ) }
52
+
53
+ it "should add the path argument to the uri's path" do
54
+ expect(subject.format_url(url, path).path).to eq("/path/_bulk")
55
+ end
56
+
57
+ context "when uri contains query parameters" do
58
+ let(:query_params) { "query=value&key=value2" }
59
+ let(:url) { ::LogStash::Util::SafeURI.new("http://localhost:9200/path/?#{query_params}") }
60
+ let(:formatted) { subject.format_url(url, path)}
61
+
62
+
63
+ it "should retain query_params after format" do
64
+ expect(formatted.query).to eq(query_params)
65
+ end
66
+
67
+ context "and the path contains query parameters" do
68
+ let(:path) { "/special_path?specialParam=123" }
69
+
70
+ it "should join the query correctly" do
71
+ expect(formatted.query).to eq(query_params + "&specialParam=123")
72
+ end
73
+ end
74
+ end
75
+
76
+ context "when the path contains query parameters" do
77
+ let(:path) { "/special_bulk?pathParam=1"}
78
+ let(:formatted) { subject.format_url(url, path) }
79
+
80
+ it "should add the path correctly" do
81
+ expect(formatted.path).to eq("#{url.path}special_bulk")
82
+ end
83
+
84
+ it "should add the query parameters correctly" do
85
+ expect(formatted.query).to eq("pathParam=1")
86
+ end
87
+ end
88
+
89
+ context "when uri contains credentials" do
90
+ let(:url) { ::LogStash::Util::SafeURI.new("http://myuser:mypass@localhost:9200") }
91
+ let(:formatted) { subject.format_url(url, path) }
92
+
93
+ it "should remove credentials after format" do
94
+ expect(formatted.userinfo).to be_nil
95
+ end
96
+ end
97
+ end
98
+
99
+ describe "integration specs", :integration => true do
100
+ it "should perform correct tests without error" do
101
+ resp = subject.perform_request(::LogStash::Util::SafeURI.new("http://localhost:9200"), :get, "/")
102
+ expect(resp.code).to eql(200)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,198 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/outputs/amazon_es/http_client"
3
+ require "json"
4
+
5
+ describe LogStash::Outputs::ElasticSearch::HttpClient::Pool do
6
+ let(:logger) { Cabin::Channel.get }
7
+ let(:adapter) { LogStash::Outputs::ElasticSearch::HttpClient::ManticoreAdapter.new(logger,
8
+ {:aws_access_key_id => 'AAAAAAAAAAAAAAAAAAAA',
9
+ :aws_secret_access_key => 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'}) }
10
+ let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://localhost:9200")] }
11
+ let(:options) { {:resurrect_delay => 2, :url_normalizer => proc {|u| u}} } # Shorten the delay a bit to speed up tests
12
+ let(:es_node_versions) { [ "0.0.0" ] }
13
+
14
+ subject { described_class.new(logger, adapter, initial_urls, options) }
15
+
16
+ let(:manticore_double) { double("manticore a") }
17
+ before(:each) do
18
+
19
+ response_double = double("manticore response").as_null_object
20
+ # Allow healtchecks
21
+ allow(manticore_double).to receive(:head).with(any_args).and_return(response_double)
22
+ allow(manticore_double).to receive(:get).with(any_args).and_return(response_double)
23
+ allow(manticore_double).to receive(:close)
24
+
25
+ allow(::Manticore::Client).to receive(:new).and_return(manticore_double)
26
+
27
+ allow(subject).to receive(:get_es_version).with(any_args).and_return(*es_node_versions)
28
+ end
29
+
30
+ after do
31
+ subject.close
32
+ end
33
+
34
+ describe "initialization" do
35
+ it "should be successful" do
36
+ expect { subject }.not_to raise_error
37
+ subject.start
38
+ end
39
+ end
40
+
41
+ describe "the resurrectionist" do
42
+ before(:each) { subject.start }
43
+ it "should start the resurrectionist when created" do
44
+ expect(subject.resurrectionist_alive?).to eql(true)
45
+ end
46
+
47
+ it "should attempt to resurrect connections after the ressurrect delay" do
48
+ expect(subject).to receive(:healthcheck!).once
49
+ sleep(subject.resurrect_delay + 1)
50
+ end
51
+
52
+ describe "healthcheck url handling" do
53
+ let(:initial_urls) { [::LogStash::Util::SafeURI.new("http://localhost:9200")] }
54
+
55
+ context "and not setting healthcheck_path" do
56
+ it "performs the healthcheck to the root" do
57
+ expect(adapter).to receive(:perform_request) do |url, method, req_path, _, _|
58
+ expect(method).to eq(:head)
59
+ expect(url.path).to be_empty
60
+ expect(req_path).to eq("/")
61
+ end
62
+ subject.healthcheck!
63
+ end
64
+ end
65
+
66
+ context "and setting healthcheck_path" do
67
+ let(:healthcheck_path) { "/my/health" }
68
+ let(:options) { super.merge(:healthcheck_path => healthcheck_path) }
69
+ it "performs the healthcheck to the healthcheck_path" do
70
+ expect(adapter).to receive(:perform_request) do |url, method, req_path, _, _|
71
+ expect(method).to eq(:head)
72
+ expect(url.path).to be_empty
73
+ expect(req_path).to eq(healthcheck_path)
74
+ end
75
+ subject.healthcheck!
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "the sniffer" do
82
+ before(:each) { subject.start }
83
+ it "should not start the sniffer by default" do
84
+ expect(subject.sniffer_alive?).to eql(nil)
85
+ end
86
+
87
+ context "when enabled" do
88
+ let(:options) { super.merge(:sniffing => true)}
89
+
90
+ it "should start the sniffer" do
91
+ expect(subject.sniffer_alive?).to eql(true)
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "closing" do
97
+ before do
98
+ subject.start
99
+ # Simulate a single in use connection on the first check of this
100
+ allow(adapter).to receive(:close).and_call_original
101
+ allow(subject).to receive(:wait_for_in_use_connections).and_call_original
102
+ allow(subject).to receive(:in_use_connections).and_return([subject.empty_url_meta()],[])
103
+ allow(subject).to receive(:start)
104
+ subject.close
105
+ end
106
+
107
+ it "should close the adapter" do
108
+ expect(adapter).to have_received(:close)
109
+ end
110
+
111
+ it "should stop the resurrectionist" do
112
+ expect(subject.resurrectionist_alive?).to eql(false)
113
+ end
114
+
115
+ it "should stop the sniffer" do
116
+ # If no sniffer (the default) returns nil
117
+ expect(subject.sniffer_alive?).to be_falsey
118
+ end
119
+
120
+ it "should wait for in use connections to terminate" do
121
+ expect(subject).to have_received(:wait_for_in_use_connections).once
122
+ expect(subject).to have_received(:in_use_connections).twice
123
+ end
124
+ end
125
+
126
+ describe "connection management" do
127
+ before(:each) { subject.start }
128
+ context "with only one URL in the list" do
129
+ it "should use the only URL in 'with_connection'" do
130
+ subject.with_connection do |c|
131
+ expect(c).to eq(initial_urls.first)
132
+ end
133
+ end
134
+ end
135
+
136
+ context "with multiple URLs in the list" do
137
+ before :each do
138
+ allow(adapter).to receive(:perform_request).with(anything, :head, subject.healthcheck_path, {}, nil)
139
+ end
140
+ let(:initial_urls) { [ ::LogStash::Util::SafeURI.new("http://localhost:9200") ] }
141
+
142
+ it "should minimize the number of connections to a single URL" do
143
+ connected_urls = []
144
+
145
+ # If we make 2x the number requests as we have URLs we should
146
+ # connect to each URL exactly 2 times
147
+ (initial_urls.size*2).times do
148
+ u, meta = subject.get_connection
149
+ connected_urls << u
150
+ end
151
+
152
+ connected_urls.each {|u| subject.return_connection(u) }
153
+ initial_urls.each do |url|
154
+ conn_count = connected_urls.select {|u| u == url}.size
155
+ expect(conn_count).to eql(2)
156
+ end
157
+ end
158
+
159
+ it "should correctly resurrect the dead" do
160
+ u,m = subject.get_connection
161
+
162
+ # The resurrectionist will call this to check on the backend
163
+ response = double("response")
164
+ expect(adapter).to receive(:perform_request).with(u, :head, subject.healthcheck_path, {}, nil).and_return(response)
165
+
166
+ subject.return_connection(u)
167
+ subject.mark_dead(u, Exception.new)
168
+
169
+ expect(subject.url_meta(u)[:state]).to eql(:dead)
170
+ sleep subject.resurrect_delay + 1
171
+ expect(subject.url_meta(u)[:state]).to eql(:alive)
172
+ end
173
+ end
174
+ end
175
+
176
+ describe "version tracking" do
177
+ let(:initial_urls) { [
178
+ ::LogStash::Util::SafeURI.new("http://somehost:9200"),
179
+ ::LogStash::Util::SafeURI.new("http://otherhost:9201")
180
+ ] }
181
+
182
+ before(:each) do
183
+ allow(subject).to receive(:perform_request_to_url).and_return(nil)
184
+ subject.start
185
+ end
186
+
187
+ it "picks the largest major version" do
188
+ expect(subject.maximum_seen_major_version).to eq(0)
189
+ end
190
+
191
+ context "if there are nodes with multiple major versions" do
192
+ let(:es_node_versions) { [ "0.0.0", "6.0.0" ] }
193
+ it "picks the largest major version" do
194
+ expect(subject.maximum_seen_major_version).to eq(6)
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,222 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/outputs/amazon_es/http_client"
3
+ require "java"
4
+
5
+ describe LogStash::Outputs::ElasticSearch::HttpClient do
6
+ let(:ssl) { nil }
7
+ let(:base_options) do
8
+ opts = {
9
+ :hosts => [::LogStash::Util::SafeURI.new("127.0.0.1")],
10
+ :logger => Cabin::Channel.get,
11
+ :metric => ::LogStash::Instrument::NullMetric.new(:dummy).namespace(:alsodummy),
12
+ :protocol => "http",
13
+ :port => 9200,
14
+ :aws_access_key_id => "AAAAAAAAAAAAAAAAAAAA",
15
+ :aws_secret_access_key => "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
16
+ }
17
+
18
+ if !ssl.nil? # Shortcut to set this
19
+ opts[:client_settings] = {:ssl => {:enabled => ssl}}
20
+ end
21
+
22
+ opts
23
+ end
24
+
25
+ describe "Host/URL Parsing" do
26
+ subject { described_class.new(base_options) }
27
+
28
+ let(:true_hostname) { "my-dash.hostname" }
29
+ let(:ipv6_hostname) { "[::1]" }
30
+ let(:ipv4_hostname) { "127.0.0.1" }
31
+ let(:port) { 9200 }
32
+ let(:protocol) {"http"}
33
+ let(:aws_access_key_id) {"AAAAAAAAAAAAAAAAAAAA"}
34
+ let(:aws_secret_access_key) {"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}
35
+ let(:hostname_port) { "#{hostname}:#{port}" }
36
+ let(:hostname_port_uri) { ::LogStash::Util::SafeURI.new("//#{hostname_port}") }
37
+ let(:http_hostname_port) { ::LogStash::Util::SafeURI.new("http://#{hostname_port}") }
38
+ let(:https_hostname_port) { ::LogStash::Util::SafeURI.new("https://#{hostname_port}") }
39
+ let(:http_hostname_port_path) { ::LogStash::Util::SafeURI.new("http://#{hostname_port}/path") }
40
+
41
+ shared_examples("proper host handling") do
42
+ it "should properly transform a host:port string to a URL" do
43
+ expect(subject.host_to_url(hostname_port_uri).to_s).to eq(http_hostname_port.to_s + "/")
44
+ end
45
+
46
+ it "should not raise an error with a / for a path" do
47
+ expect(subject.host_to_url(::LogStash::Util::SafeURI.new("#{http_hostname_port}/"))).to eq(LogStash::Util::SafeURI.new("#{http_hostname_port}/"))
48
+ end
49
+
50
+ it "should parse full URLs correctly" do
51
+ expect(subject.host_to_url(http_hostname_port).to_s).to eq(http_hostname_port.to_s + "/")
52
+ end
53
+
54
+
55
+ describe "path" do
56
+ let(:url) { http_hostname_port_path }
57
+ let(:base_options) { super.merge(:hosts => [url]) }
58
+
59
+ it "should allow paths in a url" do
60
+ expect(subject.host_to_url(url)).to eq(url)
61
+ end
62
+
63
+ context "with the path option set" do
64
+ let(:base_options) { super.merge(:client_settings => {:path => "/otherpath"}) }
65
+
66
+ it "should not allow paths in two places" do
67
+ expect {
68
+ subject.host_to_url(url)
69
+ }.to raise_error(LogStash::ConfigurationError)
70
+ end
71
+ end
72
+
73
+ context "with a path missing a leading /" do
74
+ let(:url) { http_hostname_port }
75
+ let(:base_options) { super.merge(:client_settings => {:path => "otherpath"}) }
76
+
77
+
78
+ it "should automatically insert a / in front of path overlays" do
79
+ expected = url.clone
80
+ expected.path = url.path + "/otherpath"
81
+ expect(subject.host_to_url(url)).to eq(expected)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ describe "an regular hostname" do
88
+ let(:hostname) { true_hostname }
89
+ include_examples("proper host handling")
90
+ end
91
+
92
+ describe "an ipv4 host" do
93
+ let(:hostname) { ipv4_hostname }
94
+ include_examples("proper host handling")
95
+ end
96
+
97
+ end
98
+
99
+ describe "get" do
100
+ subject { described_class.new(base_options) }
101
+ let(:body) { "foobar" }
102
+ let(:path) { "/hello-id" }
103
+ let(:get_response) {
104
+ double("response", :body => LogStash::Json::dump( { "body" => body }))
105
+ }
106
+
107
+ it "returns the hash response" do
108
+ expect(subject.pool).to receive(:get).with(path, nil).and_return(get_response)
109
+ expect(subject.get(path)["body"]).to eq(body)
110
+ end
111
+ end
112
+
113
+ describe "join_bulk_responses" do
114
+ subject { described_class.new(base_options) }
115
+
116
+ context "when items key is available" do
117
+ require "json"
118
+ let(:bulk_response) {
119
+ LogStash::Json.load ('[{
120
+ "items": [{
121
+ "delete": {
122
+ "_index": "website",
123
+ "_type": "blog",
124
+ "_id": "123",
125
+ "_version": 2,
126
+ "status": 200,
127
+ "found": true
128
+ }
129
+ }],
130
+ "errors": false
131
+ }]')
132
+ }
133
+ it "should be handled properly" do
134
+ s = subject.send(:join_bulk_responses, bulk_response)
135
+ expect(s["errors"]).to be false
136
+ expect(s["items"].size).to be 1
137
+ end
138
+ end
139
+
140
+ context "when items key is not available" do
141
+ require "json"
142
+ let(:bulk_response) {
143
+ JSON.parse ('[{
144
+ "took": 4,
145
+ "errors": false
146
+ }]')
147
+ }
148
+ it "should be handled properly" do
149
+ s = subject.send(:join_bulk_responses, bulk_response)
150
+ expect(s["errors"]).to be false
151
+ expect(s["items"].size).to be 0
152
+ end
153
+ end
154
+ end
155
+
156
+ describe "#bulk" do
157
+ subject { described_class.new(base_options) }
158
+
159
+ require "json"
160
+ let(:message) { "hey" }
161
+ let(:actions) { [
162
+ ["index", {:_id=>nil, :_index=>"logstash"}, {"message"=> message}],
163
+ ]}
164
+
165
+ context "if a message is over TARGET_BULK_BYTES" do
166
+ let(:target_bulk_bytes) { LogStash::Outputs::ElasticSearch::TARGET_BULK_BYTES }
167
+ let(:message) { "a" * (target_bulk_bytes + 1) }
168
+
169
+ it "should be handled properly" do
170
+ allow(subject).to receive(:join_bulk_responses)
171
+ expect(subject).to receive(:bulk_send).once do |data|
172
+ expect(data.size).to be > target_bulk_bytes
173
+ end
174
+ s = subject.send(:bulk, actions)
175
+ end
176
+ end
177
+
178
+ context "with two messages" do
179
+ let(:message1) { "hey" }
180
+ let(:message2) { "you" }
181
+ let(:actions) { [
182
+ ["index", {:_id=>nil, :_index=>"logstash"}, {"message"=> message1}],
183
+ ["index", {:_id=>nil, :_index=>"logstash"}, {"message"=> message2}],
184
+ ]}
185
+ it "executes one bulk_send operation" do
186
+ allow(subject).to receive(:join_bulk_responses)
187
+ expect(subject).to receive(:bulk_send).once
188
+ s = subject.send(:bulk, actions)
189
+ end
190
+
191
+ context "if one exceeds TARGET_BULK_BYTES" do
192
+ let(:target_bulk_bytes) { LogStash::Outputs::ElasticSearch::TARGET_BULK_BYTES }
193
+ let(:message1) { "a" * (target_bulk_bytes + 1) }
194
+ it "executes two bulk_send operations" do
195
+ allow(subject).to receive(:join_bulk_responses)
196
+ expect(subject).to receive(:bulk_send).twice
197
+ s = subject.send(:bulk, actions)
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ describe "sniffing" do
204
+ let(:client) { LogStash::Outputs::ElasticSearch::HttpClient.new(base_options.merge(client_opts)) }
205
+
206
+ context "with sniffing enabled" do
207
+ let(:client_opts) { {:sniffing => true, :sniffing_delay => 1 } }
208
+
209
+ it "should start the sniffer" do
210
+ expect(client.pool.sniffing).to be_truthy
211
+ end
212
+ end
213
+
214
+ context "with sniffing disabled" do
215
+ let(:client_opts) { {:sniffing => false} }
216
+
217
+ it "should not start the sniffer" do
218
+ expect(client.pool.sniffing).to be_falsey
219
+ end
220
+ end
221
+ end
222
+ end