logstash-output-amazon_es 2.0.1-java → 6.4.0-java

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