logstash-output-elasticsearch-test 11.16.0-x86_64-linux
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +649 -0
- data/CONTRIBUTORS +34 -0
- data/Gemfile +16 -0
- data/LICENSE +202 -0
- data/NOTICE.TXT +5 -0
- data/README.md +106 -0
- data/docs/index.asciidoc +1369 -0
- data/lib/logstash/outputs/elasticsearch/data_stream_support.rb +282 -0
- data/lib/logstash/outputs/elasticsearch/default-ilm-policy.json +14 -0
- data/lib/logstash/outputs/elasticsearch/http_client/manticore_adapter.rb +155 -0
- data/lib/logstash/outputs/elasticsearch/http_client/pool.rb +534 -0
- data/lib/logstash/outputs/elasticsearch/http_client.rb +497 -0
- data/lib/logstash/outputs/elasticsearch/http_client_builder.rb +201 -0
- data/lib/logstash/outputs/elasticsearch/ilm.rb +92 -0
- data/lib/logstash/outputs/elasticsearch/license_checker.rb +52 -0
- data/lib/logstash/outputs/elasticsearch/template_manager.rb +131 -0
- data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-6x.json +45 -0
- data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-7x.json +44 -0
- data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-8x.json +50 -0
- data/lib/logstash/outputs/elasticsearch.rb +699 -0
- data/lib/logstash/plugin_mixins/elasticsearch/api_configs.rb +237 -0
- data/lib/logstash/plugin_mixins/elasticsearch/common.rb +409 -0
- data/lib/logstash/plugin_mixins/elasticsearch/noop_license_checker.rb +9 -0
- data/logstash-output-elasticsearch.gemspec +40 -0
- data/spec/es_spec_helper.rb +225 -0
- data/spec/fixtures/_nodes/6x.json +81 -0
- data/spec/fixtures/_nodes/7x.json +92 -0
- data/spec/fixtures/htpasswd +2 -0
- data/spec/fixtures/license_check/active.json +16 -0
- data/spec/fixtures/license_check/inactive.json +5 -0
- data/spec/fixtures/nginx_reverse_proxy.conf +22 -0
- data/spec/fixtures/scripts/painless/scripted_update.painless +2 -0
- data/spec/fixtures/scripts/painless/scripted_update_nested.painless +1 -0
- data/spec/fixtures/scripts/painless/scripted_upsert.painless +1 -0
- data/spec/fixtures/template-with-policy-es6x.json +48 -0
- data/spec/fixtures/template-with-policy-es7x.json +45 -0
- data/spec/fixtures/template-with-policy-es8x.json +50 -0
- data/spec/fixtures/test_certs/ca.crt +29 -0
- data/spec/fixtures/test_certs/ca.der.sha256 +1 -0
- data/spec/fixtures/test_certs/ca.key +51 -0
- data/spec/fixtures/test_certs/renew.sh +13 -0
- data/spec/fixtures/test_certs/test.crt +30 -0
- data/spec/fixtures/test_certs/test.der.sha256 +1 -0
- data/spec/fixtures/test_certs/test.key +51 -0
- data/spec/fixtures/test_certs/test.p12 +0 -0
- data/spec/fixtures/test_certs/test_invalid.crt +36 -0
- data/spec/fixtures/test_certs/test_invalid.key +51 -0
- data/spec/fixtures/test_certs/test_invalid.p12 +0 -0
- data/spec/fixtures/test_certs/test_self_signed.crt +32 -0
- data/spec/fixtures/test_certs/test_self_signed.key +54 -0
- data/spec/fixtures/test_certs/test_self_signed.p12 +0 -0
- data/spec/integration/outputs/compressed_indexing_spec.rb +70 -0
- data/spec/integration/outputs/create_spec.rb +67 -0
- data/spec/integration/outputs/data_stream_spec.rb +68 -0
- data/spec/integration/outputs/delete_spec.rb +63 -0
- data/spec/integration/outputs/ilm_spec.rb +534 -0
- data/spec/integration/outputs/index_spec.rb +421 -0
- data/spec/integration/outputs/index_version_spec.rb +98 -0
- data/spec/integration/outputs/ingest_pipeline_spec.rb +75 -0
- data/spec/integration/outputs/metrics_spec.rb +66 -0
- data/spec/integration/outputs/no_es_on_startup_spec.rb +78 -0
- data/spec/integration/outputs/painless_update_spec.rb +99 -0
- data/spec/integration/outputs/parent_spec.rb +94 -0
- data/spec/integration/outputs/retry_spec.rb +182 -0
- data/spec/integration/outputs/routing_spec.rb +61 -0
- data/spec/integration/outputs/sniffer_spec.rb +94 -0
- data/spec/integration/outputs/templates_spec.rb +133 -0
- data/spec/integration/outputs/unsupported_actions_spec.rb +75 -0
- data/spec/integration/outputs/update_spec.rb +114 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/elasticsearch/api/actions/delete_ilm_policy.rb +19 -0
- data/spec/support/elasticsearch/api/actions/get_alias.rb +18 -0
- data/spec/support/elasticsearch/api/actions/get_ilm_policy.rb +18 -0
- data/spec/support/elasticsearch/api/actions/put_alias.rb +24 -0
- data/spec/support/elasticsearch/api/actions/put_ilm_policy.rb +25 -0
- data/spec/unit/http_client_builder_spec.rb +185 -0
- data/spec/unit/outputs/elasticsearch/data_stream_support_spec.rb +612 -0
- data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +151 -0
- data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +501 -0
- data/spec/unit/outputs/elasticsearch/http_client_spec.rb +339 -0
- data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +189 -0
- data/spec/unit/outputs/elasticsearch_proxy_spec.rb +103 -0
- data/spec/unit/outputs/elasticsearch_spec.rb +1573 -0
- data/spec/unit/outputs/elasticsearch_ssl_spec.rb +197 -0
- data/spec/unit/outputs/error_whitelist_spec.rb +56 -0
- data/spec/unit/outputs/license_check_spec.rb +57 -0
- metadata +423 -0
@@ -0,0 +1,237 @@
|
|
1
|
+
|
2
|
+
require 'logstash/plugin_mixins/ca_trusted_fingerprint_support'
|
3
|
+
|
4
|
+
module LogStash; module PluginMixins; module ElasticSearch
|
5
|
+
module APIConfigs
|
6
|
+
|
7
|
+
# This module defines common options that can be reused by alternate elasticsearch output plugins such as the elasticsearch_data_streams output.
|
8
|
+
|
9
|
+
DEFAULT_HOST = ::LogStash::Util::SafeURI.new("//127.0.0.1")
|
10
|
+
|
11
|
+
CONFIG_PARAMS = {
|
12
|
+
# Username to authenticate to a secure Elasticsearch cluster
|
13
|
+
:user => { :validate => :string },
|
14
|
+
# Password to authenticate to a secure Elasticsearch cluster
|
15
|
+
:password => { :validate => :password },
|
16
|
+
|
17
|
+
# Authenticate using Elasticsearch API key.
|
18
|
+
# format is id:api_key (as returned by https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html[Create API key])
|
19
|
+
:api_key => { :validate => :password },
|
20
|
+
|
21
|
+
# Cloud authentication string ("<username>:<password>" format) is an alternative for the `user`/`password` configuration.
|
22
|
+
#
|
23
|
+
# For more details, check out the https://www.elastic.co/guide/en/logstash/current/connecting-to-cloud.html#_cloud_auth[cloud documentation]
|
24
|
+
:cloud_auth => { :validate => :password },
|
25
|
+
|
26
|
+
# The document ID for the index. Useful for overwriting existing entries in
|
27
|
+
# Elasticsearch with the same ID.
|
28
|
+
:document_id => { :validate => :string },
|
29
|
+
|
30
|
+
# HTTP Path at which the Elasticsearch server lives. Use this if you must run Elasticsearch behind a proxy that remaps
|
31
|
+
# the root path for the Elasticsearch HTTP API lives.
|
32
|
+
# Note that if you use paths as components of URLs in the 'hosts' field you may
|
33
|
+
# not also set this field. That will raise an error at startup
|
34
|
+
:path => { :validate => :string },
|
35
|
+
|
36
|
+
# HTTP Path to perform the _bulk requests to
|
37
|
+
# this defaults to a concatenation of the path parameter and "_bulk"
|
38
|
+
:bulk_path => { :validate => :string },
|
39
|
+
|
40
|
+
# Pass a set of key value pairs as the URL query string. This query string is added
|
41
|
+
# to every host listed in the 'hosts' configuration. If the 'hosts' list contains
|
42
|
+
# urls that already have query strings, the one specified here will be appended.
|
43
|
+
:parameters => { :validate => :hash },
|
44
|
+
|
45
|
+
# Enable SSL/TLS secured communication to Elasticsearch cluster. Leaving this unspecified will use whatever scheme
|
46
|
+
# is specified in the URLs listed in 'hosts'. If no explicit protocol is specified plain HTTP will be used.
|
47
|
+
# If SSL is explicitly disabled here the plugin will refuse to start if an HTTPS URL is given in 'hosts'
|
48
|
+
:ssl => { :validate => :boolean, :deprecated => "Set 'ssl_enabled' instead." },
|
49
|
+
|
50
|
+
# Enable SSL/TLS secured communication to Elasticsearch cluster. Leaving this unspecified will use whatever scheme
|
51
|
+
# is specified in the URLs listed in 'hosts'. If no explicit protocol is specified plain HTTP will be used.
|
52
|
+
# If SSL is explicitly disabled here the plugin will refuse to start if an HTTPS URL is given in 'hosts'
|
53
|
+
:ssl_enabled => { :validate => :boolean },
|
54
|
+
|
55
|
+
# Option to validate the server's certificate. Disabling this severely compromises security.
|
56
|
+
# For more information on disabling certificate verification please read
|
57
|
+
# https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf
|
58
|
+
:ssl_certificate_verification => { :validate => :boolean, :default => true, :deprecated => "Set 'ssl_verification_mode' instead." },
|
59
|
+
|
60
|
+
# Options to verify the server's certificate.
|
61
|
+
# "full": validates that the provided certificate has an issue date that’s within the not_before and not_after dates;
|
62
|
+
# chains to a trusted Certificate Authority (CA); has a hostname or IP address that matches the names within the certificate.
|
63
|
+
# "none": performs no certificate validation. Disabling this severely compromises security (https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf)
|
64
|
+
:ssl_verification_mode => { :validate => %w[full none], :default => 'full' },
|
65
|
+
|
66
|
+
# The .cer or .pem file to validate the server's certificate
|
67
|
+
:cacert => { :validate => :path, :deprecated => "Set 'ssl_certificate_authorities' instead." },
|
68
|
+
|
69
|
+
# The .cer or .pem files to validate the server's certificate
|
70
|
+
:ssl_certificate_authorities => { :validate => :path, :list => true },
|
71
|
+
|
72
|
+
# One or more hex-encoded SHA256 fingerprints to trust as Certificate Authorities
|
73
|
+
:ca_trusted_fingerprint => LogStash::PluginMixins::CATrustedFingerprintSupport,
|
74
|
+
|
75
|
+
# The JKS truststore to validate the server's certificate.
|
76
|
+
# Use either `:truststore` or `:cacert`
|
77
|
+
:truststore => { :validate => :path, :deprecated => "Set 'ssl_truststore_path' instead." },
|
78
|
+
|
79
|
+
# The JKS truststore to validate the server's certificate.
|
80
|
+
# Use either `:ssl_truststore_path` or `:ssl_certificate_authorities`
|
81
|
+
:ssl_truststore_path => { :validate => :path },
|
82
|
+
|
83
|
+
# The format of the truststore file. It must be either jks or pkcs12
|
84
|
+
:ssl_truststore_type => { :validate => %w[pkcs12 jks] },
|
85
|
+
|
86
|
+
# Set the truststore password
|
87
|
+
:truststore_password => { :validate => :password, :deprecated => "Use 'ssl_truststore_password' instead." },
|
88
|
+
|
89
|
+
# Set the truststore password
|
90
|
+
:ssl_truststore_password => { :validate => :password },
|
91
|
+
|
92
|
+
# The keystore used to present a certificate to the server.
|
93
|
+
# It can be either .jks or .p12
|
94
|
+
:keystore => { :validate => :path, :deprecated => "Set 'ssl_keystore_path' instead." },
|
95
|
+
|
96
|
+
# The keystore used to present a certificate to the server.
|
97
|
+
# It can be either .jks or .p12
|
98
|
+
:ssl_keystore_path => { :validate => :path },
|
99
|
+
|
100
|
+
# The format of the keystore file. It must be either jks or pkcs12
|
101
|
+
:ssl_keystore_type => { :validate => %w[pkcs12 jks] },
|
102
|
+
|
103
|
+
# Set the keystore password
|
104
|
+
:keystore_password => { :validate => :password, :deprecated => "Set 'ssl_keystore_password' instead." },
|
105
|
+
|
106
|
+
# Set the keystore password
|
107
|
+
:ssl_keystore_password => { :validate => :password },
|
108
|
+
|
109
|
+
:ssl_supported_protocols => { :validate => ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'], :default => [], :list => true },
|
110
|
+
|
111
|
+
# OpenSSL-style X.509 certificate certificate to authenticate the client
|
112
|
+
:ssl_certificate => { :validate => :path },
|
113
|
+
|
114
|
+
# OpenSSL-style RSA private key to authenticate the client
|
115
|
+
:ssl_key => { :validate => :path },
|
116
|
+
|
117
|
+
# The list of cipher suites to use, listed by priorities.
|
118
|
+
# Supported cipher suites vary depending on which version of Java is used.
|
119
|
+
:ssl_cipher_suites => { :validate => :string, :list => true },
|
120
|
+
|
121
|
+
# This setting asks Elasticsearch for the list of all cluster nodes and adds them to the hosts list.
|
122
|
+
# Note: This will return ALL nodes with HTTP enabled (including master nodes!). If you use
|
123
|
+
# this with master nodes, you probably want to disable HTTP on them by setting
|
124
|
+
# `http.enabled` to false in their elasticsearch.yml. You can either use the `sniffing` option or
|
125
|
+
# manually enter multiple Elasticsearch hosts using the `hosts` parameter.
|
126
|
+
:sniffing => { :validate => :boolean, :default => false },
|
127
|
+
|
128
|
+
# How long to wait, in seconds, between sniffing attempts
|
129
|
+
:sniffing_delay => { :validate => :number, :default => 5 },
|
130
|
+
|
131
|
+
# HTTP Path to be used for the sniffing requests
|
132
|
+
# the default value is computed by concatenating the path value and "_nodes/http"
|
133
|
+
# if sniffing_path is set it will be used as an absolute path
|
134
|
+
# do not use full URL here, only paths, e.g. "/sniff/_nodes/http"
|
135
|
+
:sniffing_path => { :validate => :string },
|
136
|
+
|
137
|
+
# Set the address of a forward HTTP proxy.
|
138
|
+
# This used to accept hashes as arguments but now only accepts
|
139
|
+
# arguments of the URI type to prevent leaking credentials.
|
140
|
+
:proxy => { :validate => :uri }, # but empty string is allowed
|
141
|
+
|
142
|
+
# Set the timeout, in seconds, for network operations and requests sent Elasticsearch. If
|
143
|
+
# a timeout occurs, the request will be retried.
|
144
|
+
:timeout => { :validate => :number, :default => 60 },
|
145
|
+
|
146
|
+
# Deprecated, refer to `silence_errors_in_log`.
|
147
|
+
:failure_type_logging_whitelist => { :validate => :array, :default => [] },
|
148
|
+
|
149
|
+
# Defines the list of Elasticsearch errors that you don't want to log.
|
150
|
+
# A useful example is when you want to skip all 409 errors
|
151
|
+
# which are `document_already_exists_exception`.
|
152
|
+
# Deprecates `failure_type_logging_whitelist`.
|
153
|
+
:silence_errors_in_log => { :validate => :array, :default => [] },
|
154
|
+
|
155
|
+
# While the output tries to reuse connections efficiently we have a maximum.
|
156
|
+
# This sets the maximum number of open connections the output will create.
|
157
|
+
# Setting this too low may mean frequently closing / opening connections
|
158
|
+
# which is bad.
|
159
|
+
:pool_max => { :validate => :number, :default => 1000 },
|
160
|
+
|
161
|
+
# While the output tries to reuse connections efficiently we have a maximum per endpoint.
|
162
|
+
# This sets the maximum number of open connections per endpoint the output will create.
|
163
|
+
# Setting this too low may mean frequently closing / opening connections
|
164
|
+
# which is bad.
|
165
|
+
:pool_max_per_route => { :validate => :number, :default => 100 },
|
166
|
+
|
167
|
+
# HTTP Path where a HEAD request is sent when a backend is marked down
|
168
|
+
# the request is sent in the background to see if it has come back again
|
169
|
+
# before it is once again eligible to service requests.
|
170
|
+
# If you have custom firewall rules you may need to change this
|
171
|
+
:healthcheck_path => { :validate => :string },
|
172
|
+
|
173
|
+
# How frequently, in seconds, to wait between resurrection attempts.
|
174
|
+
# Resurrection is the process by which backend endpoints marked 'down' are checked
|
175
|
+
# to see if they have come back to life
|
176
|
+
:resurrect_delay => { :validate => :number, :default => 5 },
|
177
|
+
|
178
|
+
# How long to wait before checking if the connection is stale before executing a request on a connection using keepalive.
|
179
|
+
# You may want to set this lower, if you get connection errors regularly
|
180
|
+
# Quoting the Apache commons docs (this client is based Apache Commmons):
|
181
|
+
# 'Defines period of inactivity in milliseconds after which persistent connections must
|
182
|
+
# be re-validated prior to being leased to the consumer. Non-positive value passed to
|
183
|
+
# this method disables connection validation. This check helps detect connections that
|
184
|
+
# have become stale (half-closed) while kept inactive in the pool.'
|
185
|
+
# See https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/conn/PoolingHttpClientConnectionManager.html#setValidateAfterInactivity(int)[these docs for more info]
|
186
|
+
:validate_after_inactivity => { :validate => :number, :default => 10000 },
|
187
|
+
|
188
|
+
# Enable gzip compression on requests. Note that response compression is on by default for Elasticsearch v5.0 and beyond
|
189
|
+
:http_compression => { :validate => :boolean, :default => false },
|
190
|
+
|
191
|
+
# Custom Headers to send on each request to elasticsearch nodes
|
192
|
+
:custom_headers => { :validate => :hash, :default => {} },
|
193
|
+
|
194
|
+
# Sets the host(s) of the remote instance. If given an array it will load balance requests across the hosts specified in the `hosts` parameter.
|
195
|
+
# Remember the `http` protocol uses the http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-http.html#modules-http[http] address (eg. 9200, not 9300).
|
196
|
+
# `"127.0.0.1"`
|
197
|
+
# `["127.0.0.1:9200","127.0.0.2:9200"]`
|
198
|
+
# `["http://127.0.0.1"]`
|
199
|
+
# `["https://127.0.0.1:9200"]`
|
200
|
+
# `["https://127.0.0.1:9200/mypath"]` (If using a proxy on a subpath)
|
201
|
+
# It is important to exclude http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html[dedicated master nodes] from the `hosts` list
|
202
|
+
# to prevent LS from sending bulk requests to the master nodes. So this parameter should only reference either data or client nodes in Elasticsearch.
|
203
|
+
#
|
204
|
+
# Any special characters present in the URLs here MUST be URL escaped! This means `#` should be put in as `%23` for instance.
|
205
|
+
:hosts => { :validate => :uri, :default => [ DEFAULT_HOST ], :list => true },
|
206
|
+
|
207
|
+
# Cloud ID, from the Elastic Cloud web console. If set `hosts` should not be used.
|
208
|
+
#
|
209
|
+
# For more details, check out the https://www.elastic.co/guide/en/logstash/current/connecting-to-cloud.html#_cloud_id[cloud documentation]
|
210
|
+
:cloud_id => { :validate => :string },
|
211
|
+
|
212
|
+
# Set initial interval in seconds between bulk retries. Doubled on each retry up to `retry_max_interval`
|
213
|
+
:retry_initial_interval => { :validate => :number, :default => 2 },
|
214
|
+
|
215
|
+
# Set max interval in seconds between bulk retries.
|
216
|
+
:retry_max_interval => { :validate => :number, :default => 64 },
|
217
|
+
|
218
|
+
# List extra HTTP's error codes that are considered valid to move the events into the dead letter queue.
|
219
|
+
# It's considered a configuration error to re-use the same predefined codes for success, DLQ or conflict.
|
220
|
+
# The option accepts a list of natural numbers corresponding to HTTP errors codes.
|
221
|
+
:dlq_custom_codes => { :validate => :number, :list => true, :default => [] },
|
222
|
+
|
223
|
+
# if enabled, failed index name interpolation events go into dead letter queue.
|
224
|
+
:dlq_on_failed_indexname_interpolation => { :validate => :boolean, :default => true }
|
225
|
+
}.freeze
|
226
|
+
|
227
|
+
def self.included(base)
|
228
|
+
CONFIG_PARAMS.each do |name, opts|
|
229
|
+
if opts.kind_of?(Module)
|
230
|
+
base.include(opts)
|
231
|
+
else
|
232
|
+
base.config(name, opts)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end; end; end
|
@@ -0,0 +1,409 @@
|
|
1
|
+
require "logstash/outputs/elasticsearch/template_manager"
|
2
|
+
|
3
|
+
module LogStash; module PluginMixins; module ElasticSearch
|
4
|
+
module Common
|
5
|
+
|
6
|
+
# This module defines common methods that can be reused by alternate elasticsearch output plugins such as the elasticsearch_data_streams output.
|
7
|
+
|
8
|
+
attr_reader :hosts
|
9
|
+
|
10
|
+
# These codes apply to documents, not at the request level
|
11
|
+
DOC_DLQ_CODES = [400, 404]
|
12
|
+
DOC_SUCCESS_CODES = [200, 201]
|
13
|
+
DOC_CONFLICT_CODE = 409
|
14
|
+
|
15
|
+
# Perform some ES options validations and Build the HttpClient.
|
16
|
+
# Note that this methods may sets the @user, @password, @hosts and @client ivars as a side effect.
|
17
|
+
# @param license_checker [#appropriate_license?] An optional license checker that will be used by the Pool class.
|
18
|
+
# @return [HttpClient] the new http client
|
19
|
+
def build_client(license_checker = nil)
|
20
|
+
params["license_checker"] = license_checker
|
21
|
+
|
22
|
+
# the following 3 options validation & setup methods are called inside build_client
|
23
|
+
# because they must be executed prior to building the client and logstash
|
24
|
+
# monitoring and management rely on directly calling build_client
|
25
|
+
# see https://github.com/logstash-plugins/logstash-output-elasticsearch/pull/934#pullrequestreview-396203307
|
26
|
+
fill_hosts_from_cloud_id
|
27
|
+
validate_authentication
|
28
|
+
|
29
|
+
setup_hosts
|
30
|
+
|
31
|
+
params['ssl_enabled'] = effectively_ssl? unless params.include?('ssl_enabled')
|
32
|
+
|
33
|
+
# inject the TrustStrategy from CATrustedFingerprintSupport
|
34
|
+
if trust_strategy_for_ca_trusted_fingerprint
|
35
|
+
params["ssl_trust_strategy"] = trust_strategy_for_ca_trusted_fingerprint
|
36
|
+
end
|
37
|
+
|
38
|
+
params["metric"] = metric
|
39
|
+
if @proxy.eql?('')
|
40
|
+
@logger.warn "Supplied proxy setting (proxy => '') has no effect"
|
41
|
+
end
|
42
|
+
::LogStash::Outputs::ElasticSearch::HttpClientBuilder.build(@logger, @hosts, params)
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_authentication
|
46
|
+
authn_options = 0
|
47
|
+
authn_options += 1 if @cloud_auth
|
48
|
+
authn_options += 1 if (@api_key && @api_key.value)
|
49
|
+
authn_options += 1 if (@user || (@password && @password.value))
|
50
|
+
|
51
|
+
if authn_options > 1
|
52
|
+
raise LogStash::ConfigurationError, 'Multiple authentication options are specified, please only use one of user/password, cloud_auth or api_key'
|
53
|
+
end
|
54
|
+
|
55
|
+
if @api_key && @api_key.value && !effectively_ssl?
|
56
|
+
raise(LogStash::ConfigurationError, "Using api_key authentication requires SSL/TLS secured communication using the `ssl => true` option")
|
57
|
+
end
|
58
|
+
|
59
|
+
if @cloud_auth
|
60
|
+
@user, @password = parse_user_password_from_cloud_auth(@cloud_auth)
|
61
|
+
# params is the plugin global params hash which will be passed to HttpClientBuilder.build
|
62
|
+
params['user'], params['password'] = @user, @password
|
63
|
+
end
|
64
|
+
end
|
65
|
+
private :validate_authentication
|
66
|
+
|
67
|
+
def setup_hosts
|
68
|
+
@hosts = Array(@hosts)
|
69
|
+
if @hosts.empty?
|
70
|
+
@logger.info("No 'host' set in elasticsearch output. Defaulting to localhost")
|
71
|
+
@hosts.replace(["localhost"])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def effectively_ssl?
|
76
|
+
return @ssl_enabled unless @ssl_enabled.nil?
|
77
|
+
|
78
|
+
hosts = Array(@hosts)
|
79
|
+
return false if hosts.nil? || hosts.empty?
|
80
|
+
|
81
|
+
hosts.all? { |host| host && host.scheme == "https" }
|
82
|
+
end
|
83
|
+
|
84
|
+
def hosts_default?(hosts)
|
85
|
+
# NOTE: would be nice if pipeline allowed us a clean way to detect a config default :
|
86
|
+
hosts.is_a?(Array) && hosts.size == 1 && hosts.first.equal?(LogStash::PluginMixins::ElasticSearch::APIConfigs::DEFAULT_HOST)
|
87
|
+
end
|
88
|
+
private :hosts_default?
|
89
|
+
|
90
|
+
def fill_hosts_from_cloud_id
|
91
|
+
return unless @cloud_id
|
92
|
+
|
93
|
+
if @hosts && !hosts_default?(@hosts)
|
94
|
+
raise LogStash::ConfigurationError, 'Both cloud_id and hosts specified, please only use one of those.'
|
95
|
+
end
|
96
|
+
@hosts = parse_host_uri_from_cloud_id(@cloud_id)
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_host_uri_from_cloud_id(cloud_id)
|
100
|
+
begin # might not be available on older LS
|
101
|
+
require 'logstash/util/cloud_setting_id'
|
102
|
+
rescue LoadError
|
103
|
+
raise LogStash::ConfigurationError, 'The cloud_id setting is not supported by your version of Logstash, ' +
|
104
|
+
'please upgrade your installation (or set hosts instead).'
|
105
|
+
end
|
106
|
+
|
107
|
+
begin
|
108
|
+
cloud_id = LogStash::Util::CloudSettingId.new(cloud_id) # already does append ':{port}' to host
|
109
|
+
rescue ArgumentError => e
|
110
|
+
raise LogStash::ConfigurationError, e.message.to_s.sub(/Cloud Id/i, 'cloud_id')
|
111
|
+
end
|
112
|
+
cloud_uri = "#{cloud_id.elasticsearch_scheme}://#{cloud_id.elasticsearch_host}"
|
113
|
+
LogStash::Util::SafeURI.new(cloud_uri)
|
114
|
+
end
|
115
|
+
private :parse_host_uri_from_cloud_id
|
116
|
+
|
117
|
+
def parse_user_password_from_cloud_auth(cloud_auth)
|
118
|
+
begin # might not be available on older LS
|
119
|
+
require 'logstash/util/cloud_setting_auth'
|
120
|
+
rescue LoadError
|
121
|
+
raise LogStash::ConfigurationError, 'The cloud_auth setting is not supported by your version of Logstash, ' +
|
122
|
+
'please upgrade your installation (or set user/password instead).'
|
123
|
+
end
|
124
|
+
|
125
|
+
cloud_auth = cloud_auth.value if cloud_auth.is_a?(LogStash::Util::Password)
|
126
|
+
begin
|
127
|
+
cloud_auth = LogStash::Util::CloudSettingAuth.new(cloud_auth)
|
128
|
+
rescue ArgumentError => e
|
129
|
+
raise LogStash::ConfigurationError, e.message.to_s.sub(/Cloud Auth/i, 'cloud_auth')
|
130
|
+
end
|
131
|
+
[ cloud_auth.username, cloud_auth.password ]
|
132
|
+
end
|
133
|
+
private :parse_user_password_from_cloud_auth
|
134
|
+
|
135
|
+
# Plugin initialization extension point (after a successful ES connection).
|
136
|
+
def finish_register
|
137
|
+
end
|
138
|
+
protected :finish_register
|
139
|
+
|
140
|
+
def last_es_version
|
141
|
+
client.last_es_version
|
142
|
+
end
|
143
|
+
|
144
|
+
def maximum_seen_major_version
|
145
|
+
client.maximum_seen_major_version
|
146
|
+
end
|
147
|
+
|
148
|
+
def serverless?
|
149
|
+
client.serverless?
|
150
|
+
end
|
151
|
+
|
152
|
+
def alive_urls_count
|
153
|
+
client.alive_urls_count
|
154
|
+
end
|
155
|
+
|
156
|
+
def successful_connection?
|
157
|
+
!!maximum_seen_major_version && alive_urls_count > 0
|
158
|
+
end
|
159
|
+
|
160
|
+
# launch a thread that waits for an initial successful connection to the ES cluster to call the given block
|
161
|
+
# @param block [Proc] the block to execute upon initial successful connection
|
162
|
+
# @return [Thread] the successful connection wait thread
|
163
|
+
def after_successful_connection(&block)
|
164
|
+
Thread.new do
|
165
|
+
sleep_interval = @retry_initial_interval
|
166
|
+
# in case of a pipeline's shutdown_requested?, the method #close shutdown also this thread
|
167
|
+
# so no need to explicitly handle it here and return an AbortedBatchException.
|
168
|
+
until successful_connection? || @stopping.true?
|
169
|
+
@logger.debug("Waiting for connectivity to Elasticsearch cluster, retrying in #{sleep_interval}s")
|
170
|
+
sleep_interval = sleep_for_interval(sleep_interval)
|
171
|
+
end
|
172
|
+
block.call if successful_connection?
|
173
|
+
end
|
174
|
+
end
|
175
|
+
private :after_successful_connection
|
176
|
+
|
177
|
+
def discover_cluster_uuid
|
178
|
+
return unless defined?(plugin_metadata)
|
179
|
+
cluster_info = client.get('/')
|
180
|
+
plugin_metadata.set(:cluster_uuid, cluster_info['cluster_uuid'])
|
181
|
+
rescue => e
|
182
|
+
@logger.error("Unable to retrieve Elasticsearch cluster uuid", message: e.message, exception: e.class, backtrace: e.backtrace)
|
183
|
+
end
|
184
|
+
|
185
|
+
def retrying_submit(actions)
|
186
|
+
# Initially we submit the full list of actions
|
187
|
+
submit_actions = actions
|
188
|
+
|
189
|
+
sleep_interval = @retry_initial_interval
|
190
|
+
|
191
|
+
while submit_actions && submit_actions.size > 0
|
192
|
+
|
193
|
+
# We retry with whatever is didn't succeed
|
194
|
+
begin
|
195
|
+
submit_actions = submit(submit_actions)
|
196
|
+
if submit_actions && submit_actions.size > 0
|
197
|
+
@logger.info("Retrying individual bulk actions that failed or were rejected by the previous bulk request", count: submit_actions.size)
|
198
|
+
end
|
199
|
+
rescue => e
|
200
|
+
if abort_batch_present? && e.instance_of?(org.logstash.execution.AbortedBatchException)
|
201
|
+
# if Logstash support abort of a batch and the batch is aborting,
|
202
|
+
# bubble up the exception so that the pipeline can handle it
|
203
|
+
raise e
|
204
|
+
else
|
205
|
+
@logger.error("Encountered an unexpected error submitting a bulk request, will retry",
|
206
|
+
message: e.message, exception: e.class, backtrace: e.backtrace)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Everything was a success!
|
211
|
+
break if !submit_actions || submit_actions.empty?
|
212
|
+
|
213
|
+
# If we're retrying the action sleep for the recommended interval
|
214
|
+
# Double the interval for the next time through to achieve exponential backoff
|
215
|
+
sleep_interval = sleep_for_interval(sleep_interval)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def sleep_for_interval(sleep_interval)
|
220
|
+
stoppable_sleep(sleep_interval)
|
221
|
+
next_sleep_interval(sleep_interval)
|
222
|
+
end
|
223
|
+
|
224
|
+
def stoppable_sleep(interval)
|
225
|
+
Stud.stoppable_sleep(interval) { @stopping.true? }
|
226
|
+
end
|
227
|
+
|
228
|
+
def next_sleep_interval(current_interval)
|
229
|
+
doubled = current_interval * 2
|
230
|
+
doubled > @retry_max_interval ? @retry_max_interval : doubled
|
231
|
+
end
|
232
|
+
|
233
|
+
def handle_dlq_response(message, action, status, response)
|
234
|
+
event, action_params = action.event, [action[0], action[1], action[2]]
|
235
|
+
|
236
|
+
if @dlq_writer
|
237
|
+
# TODO: Change this to send a map with { :status => status, :action => action } in the future
|
238
|
+
detailed_message = "#{message} status: #{status}, action: #{action_params}, response: #{response}"
|
239
|
+
@dlq_writer.write(event, "#{detailed_message}")
|
240
|
+
else
|
241
|
+
log_level = dig_value(response, 'index', 'error', 'type') == 'invalid_index_name_exception' ? :error : :warn
|
242
|
+
|
243
|
+
@logger.public_send(log_level, message, status: status, action: action_params, response: response)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
|
249
|
+
def submit(actions)
|
250
|
+
bulk_response = safe_bulk(actions)
|
251
|
+
|
252
|
+
# If the response is nil that means we were in a retry loop
|
253
|
+
# and aborted since we're shutting down
|
254
|
+
return if bulk_response.nil?
|
255
|
+
|
256
|
+
# If it did return and there are no errors we're good as well
|
257
|
+
if bulk_response["errors"]
|
258
|
+
@bulk_request_metrics.increment(:with_errors)
|
259
|
+
else
|
260
|
+
@bulk_request_metrics.increment(:successes)
|
261
|
+
@document_level_metrics.increment(:successes, actions.size)
|
262
|
+
return
|
263
|
+
end
|
264
|
+
|
265
|
+
responses = bulk_response["items"]
|
266
|
+
if responses.size != actions.size # can not map action -> response reliably
|
267
|
+
# an ES bug (on 7.10.2, 7.11.1) where a _bulk request to index X documents would return Y (> X) items
|
268
|
+
msg = "Sent #{actions.size} documents but Elasticsearch returned #{responses.size} responses"
|
269
|
+
@logger.warn(msg, actions: actions, responses: responses)
|
270
|
+
fail("#{msg} (likely a bug with _bulk endpoint)")
|
271
|
+
end
|
272
|
+
|
273
|
+
actions_to_retry = []
|
274
|
+
responses.each_with_index do |response,idx|
|
275
|
+
action_type, action_props = response.first
|
276
|
+
|
277
|
+
status = action_props["status"]
|
278
|
+
error = action_props["error"]
|
279
|
+
action = actions[idx]
|
280
|
+
|
281
|
+
# Retry logic: If it is success, we move on. If it is a failure, we have 3 paths:
|
282
|
+
# - For 409, we log and drop. there is nothing we can do
|
283
|
+
# - For a mapping error, we send to dead letter queue for a human to intervene at a later point.
|
284
|
+
# - For everything else there's mastercard. Yep, and we retry indefinitely. This should fix #572 and other transient network issues
|
285
|
+
if DOC_SUCCESS_CODES.include?(status)
|
286
|
+
@document_level_metrics.increment(:successes)
|
287
|
+
next
|
288
|
+
elsif DOC_CONFLICT_CODE == status
|
289
|
+
@document_level_metrics.increment(:non_retryable_failures)
|
290
|
+
@logger.warn "Failed action", status: status, action: action, response: response if log_failure_type?(error)
|
291
|
+
next
|
292
|
+
elsif @dlq_codes.include?(status)
|
293
|
+
handle_dlq_response("Could not index event to Elasticsearch.", action, status, response)
|
294
|
+
@document_level_metrics.increment(:dlq_routed)
|
295
|
+
next
|
296
|
+
else
|
297
|
+
# only log what the user whitelisted
|
298
|
+
@document_level_metrics.increment(:retryable_failures)
|
299
|
+
@logger.info "Retrying failed action", status: status, action: action, error: error if log_failure_type?(error)
|
300
|
+
actions_to_retry << action
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
actions_to_retry
|
305
|
+
end
|
306
|
+
|
307
|
+
def log_failure_type?(failure)
|
308
|
+
!silence_errors_in_log.include?(failure["type"])
|
309
|
+
end
|
310
|
+
|
311
|
+
# Rescue retryable errors during bulk submission
|
312
|
+
# @param actions a [action, params, event.to_hash] tuple
|
313
|
+
# @return response [Hash] which contains 'errors' and processed 'items' entries
|
314
|
+
def safe_bulk(actions)
|
315
|
+
sleep_interval = @retry_initial_interval
|
316
|
+
begin
|
317
|
+
@client.bulk(actions) # returns { 'errors': ..., 'items': ... }
|
318
|
+
rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError => e
|
319
|
+
# If we can't even connect to the server let's just print out the URL (:hosts is actually a URL)
|
320
|
+
# and let the user sort it out from there
|
321
|
+
@logger.error(
|
322
|
+
"Attempted to send a bulk request but Elasticsearch appears to be unreachable or down",
|
323
|
+
message: e.message, exception: e.class, will_retry_in_seconds: sleep_interval
|
324
|
+
)
|
325
|
+
@logger.debug? && @logger.debug("Failed actions for last bad bulk request", :actions => actions)
|
326
|
+
|
327
|
+
# We retry until there are no errors! Errors should all go to the retry queue
|
328
|
+
sleep_interval = sleep_for_interval(sleep_interval)
|
329
|
+
@bulk_request_metrics.increment(:failures)
|
330
|
+
retry unless @stopping.true?
|
331
|
+
rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::NoConnectionAvailableError => e
|
332
|
+
@logger.error(
|
333
|
+
"Attempted to send a bulk request but there are no living connections in the pool " +
|
334
|
+
"(perhaps Elasticsearch is unreachable or down?)",
|
335
|
+
message: e.message, exception: e.class, will_retry_in_seconds: sleep_interval
|
336
|
+
)
|
337
|
+
|
338
|
+
sleep_interval = sleep_for_interval(sleep_interval)
|
339
|
+
@bulk_request_metrics.increment(:failures)
|
340
|
+
if pipeline_shutdown_requested?
|
341
|
+
# when any connection is available and a shutdown is requested
|
342
|
+
# the batch can be aborted, eventually for future retry.
|
343
|
+
abort_batch_if_available!
|
344
|
+
end
|
345
|
+
retry unless @stopping.true?
|
346
|
+
rescue ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError => e
|
347
|
+
@bulk_request_metrics.increment(:failures)
|
348
|
+
log_hash = {:code => e.response_code, :url => e.url.sanitized.to_s,
|
349
|
+
:content_length => e.request_body.bytesize, :body => e.response_body}
|
350
|
+
message = "Encountered a retryable error (will retry with exponential backoff)"
|
351
|
+
|
352
|
+
# We treat 429s as a special case because these really aren't errors, but
|
353
|
+
# rather just ES telling us to back off a bit, which we do.
|
354
|
+
# The other retryable code is 503, which are true errors
|
355
|
+
# Even though we retry the user should be made aware of these
|
356
|
+
if e.response_code == 429
|
357
|
+
logger.debug(message, log_hash)
|
358
|
+
else
|
359
|
+
logger.error(message, log_hash)
|
360
|
+
end
|
361
|
+
|
362
|
+
sleep_interval = sleep_for_interval(sleep_interval)
|
363
|
+
if pipeline_shutdown_requested?
|
364
|
+
# In case ES side changes access credentials and a pipeline reload is triggered
|
365
|
+
# this error becomes a retry on restart
|
366
|
+
abort_batch_if_available!
|
367
|
+
end
|
368
|
+
retry
|
369
|
+
rescue => e # Stuff that should never happen - print out full connection issues
|
370
|
+
@logger.error(
|
371
|
+
"An unknown error occurred sending a bulk request to Elasticsearch (will retry indefinitely)",
|
372
|
+
message: e.message, exception: e.class, backtrace: e.backtrace
|
373
|
+
)
|
374
|
+
@logger.debug? && @logger.debug("Failed actions for last bad bulk request", :actions => actions)
|
375
|
+
|
376
|
+
sleep_interval = sleep_for_interval(sleep_interval)
|
377
|
+
@bulk_request_metrics.increment(:failures)
|
378
|
+
retry unless @stopping.true?
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def pipeline_shutdown_requested?
|
383
|
+
return super if defined?(super) # since LS 8.1.0
|
384
|
+
execution_context&.pipeline&.shutdown_requested?
|
385
|
+
end
|
386
|
+
|
387
|
+
def abort_batch_if_available!
|
388
|
+
raise org.logstash.execution.AbortedBatchException.new if abort_batch_present?
|
389
|
+
end
|
390
|
+
|
391
|
+
def abort_batch_present?
|
392
|
+
::Gem::Version.create(LOGSTASH_VERSION) >= ::Gem::Version.create('8.8.0')
|
393
|
+
end
|
394
|
+
|
395
|
+
def dlq_enabled?
|
396
|
+
# TODO there should be a better way to query if DLQ is enabled
|
397
|
+
# See more in: https://github.com/elastic/logstash/issues/8064
|
398
|
+
respond_to?(:execution_context) && execution_context.respond_to?(:dlq_writer) &&
|
399
|
+
!execution_context.dlq_writer.inner_writer.is_a?(::LogStash::Util::DummyDeadLetterQueueWriter)
|
400
|
+
end
|
401
|
+
|
402
|
+
def dig_value(val, first_key, *rest_keys)
|
403
|
+
fail(TypeError, "cannot dig value from #{val.class}") unless val.kind_of?(Hash)
|
404
|
+
val = val[first_key]
|
405
|
+
return val if rest_keys.empty? || val == nil
|
406
|
+
dig_value(val, *rest_keys)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end; end; end
|