logstash-output-elasticsearch-test 11.16.0-x86_64-linux

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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +649 -0
  3. data/CONTRIBUTORS +34 -0
  4. data/Gemfile +16 -0
  5. data/LICENSE +202 -0
  6. data/NOTICE.TXT +5 -0
  7. data/README.md +106 -0
  8. data/docs/index.asciidoc +1369 -0
  9. data/lib/logstash/outputs/elasticsearch/data_stream_support.rb +282 -0
  10. data/lib/logstash/outputs/elasticsearch/default-ilm-policy.json +14 -0
  11. data/lib/logstash/outputs/elasticsearch/http_client/manticore_adapter.rb +155 -0
  12. data/lib/logstash/outputs/elasticsearch/http_client/pool.rb +534 -0
  13. data/lib/logstash/outputs/elasticsearch/http_client.rb +497 -0
  14. data/lib/logstash/outputs/elasticsearch/http_client_builder.rb +201 -0
  15. data/lib/logstash/outputs/elasticsearch/ilm.rb +92 -0
  16. data/lib/logstash/outputs/elasticsearch/license_checker.rb +52 -0
  17. data/lib/logstash/outputs/elasticsearch/template_manager.rb +131 -0
  18. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-6x.json +45 -0
  19. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-7x.json +44 -0
  20. data/lib/logstash/outputs/elasticsearch/templates/ecs-disabled/elasticsearch-8x.json +50 -0
  21. data/lib/logstash/outputs/elasticsearch.rb +699 -0
  22. data/lib/logstash/plugin_mixins/elasticsearch/api_configs.rb +237 -0
  23. data/lib/logstash/plugin_mixins/elasticsearch/common.rb +409 -0
  24. data/lib/logstash/plugin_mixins/elasticsearch/noop_license_checker.rb +9 -0
  25. data/logstash-output-elasticsearch.gemspec +40 -0
  26. data/spec/es_spec_helper.rb +225 -0
  27. data/spec/fixtures/_nodes/6x.json +81 -0
  28. data/spec/fixtures/_nodes/7x.json +92 -0
  29. data/spec/fixtures/htpasswd +2 -0
  30. data/spec/fixtures/license_check/active.json +16 -0
  31. data/spec/fixtures/license_check/inactive.json +5 -0
  32. data/spec/fixtures/nginx_reverse_proxy.conf +22 -0
  33. data/spec/fixtures/scripts/painless/scripted_update.painless +2 -0
  34. data/spec/fixtures/scripts/painless/scripted_update_nested.painless +1 -0
  35. data/spec/fixtures/scripts/painless/scripted_upsert.painless +1 -0
  36. data/spec/fixtures/template-with-policy-es6x.json +48 -0
  37. data/spec/fixtures/template-with-policy-es7x.json +45 -0
  38. data/spec/fixtures/template-with-policy-es8x.json +50 -0
  39. data/spec/fixtures/test_certs/ca.crt +29 -0
  40. data/spec/fixtures/test_certs/ca.der.sha256 +1 -0
  41. data/spec/fixtures/test_certs/ca.key +51 -0
  42. data/spec/fixtures/test_certs/renew.sh +13 -0
  43. data/spec/fixtures/test_certs/test.crt +30 -0
  44. data/spec/fixtures/test_certs/test.der.sha256 +1 -0
  45. data/spec/fixtures/test_certs/test.key +51 -0
  46. data/spec/fixtures/test_certs/test.p12 +0 -0
  47. data/spec/fixtures/test_certs/test_invalid.crt +36 -0
  48. data/spec/fixtures/test_certs/test_invalid.key +51 -0
  49. data/spec/fixtures/test_certs/test_invalid.p12 +0 -0
  50. data/spec/fixtures/test_certs/test_self_signed.crt +32 -0
  51. data/spec/fixtures/test_certs/test_self_signed.key +54 -0
  52. data/spec/fixtures/test_certs/test_self_signed.p12 +0 -0
  53. data/spec/integration/outputs/compressed_indexing_spec.rb +70 -0
  54. data/spec/integration/outputs/create_spec.rb +67 -0
  55. data/spec/integration/outputs/data_stream_spec.rb +68 -0
  56. data/spec/integration/outputs/delete_spec.rb +63 -0
  57. data/spec/integration/outputs/ilm_spec.rb +534 -0
  58. data/spec/integration/outputs/index_spec.rb +421 -0
  59. data/spec/integration/outputs/index_version_spec.rb +98 -0
  60. data/spec/integration/outputs/ingest_pipeline_spec.rb +75 -0
  61. data/spec/integration/outputs/metrics_spec.rb +66 -0
  62. data/spec/integration/outputs/no_es_on_startup_spec.rb +78 -0
  63. data/spec/integration/outputs/painless_update_spec.rb +99 -0
  64. data/spec/integration/outputs/parent_spec.rb +94 -0
  65. data/spec/integration/outputs/retry_spec.rb +182 -0
  66. data/spec/integration/outputs/routing_spec.rb +61 -0
  67. data/spec/integration/outputs/sniffer_spec.rb +94 -0
  68. data/spec/integration/outputs/templates_spec.rb +133 -0
  69. data/spec/integration/outputs/unsupported_actions_spec.rb +75 -0
  70. data/spec/integration/outputs/update_spec.rb +114 -0
  71. data/spec/spec_helper.rb +10 -0
  72. data/spec/support/elasticsearch/api/actions/delete_ilm_policy.rb +19 -0
  73. data/spec/support/elasticsearch/api/actions/get_alias.rb +18 -0
  74. data/spec/support/elasticsearch/api/actions/get_ilm_policy.rb +18 -0
  75. data/spec/support/elasticsearch/api/actions/put_alias.rb +24 -0
  76. data/spec/support/elasticsearch/api/actions/put_ilm_policy.rb +25 -0
  77. data/spec/unit/http_client_builder_spec.rb +185 -0
  78. data/spec/unit/outputs/elasticsearch/data_stream_support_spec.rb +612 -0
  79. data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +151 -0
  80. data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +501 -0
  81. data/spec/unit/outputs/elasticsearch/http_client_spec.rb +339 -0
  82. data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +189 -0
  83. data/spec/unit/outputs/elasticsearch_proxy_spec.rb +103 -0
  84. data/spec/unit/outputs/elasticsearch_spec.rb +1573 -0
  85. data/spec/unit/outputs/elasticsearch_ssl_spec.rb +197 -0
  86. data/spec/unit/outputs/error_whitelist_spec.rb +56 -0
  87. data/spec/unit/outputs/license_check_spec.rb +57 -0
  88. 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
@@ -0,0 +1,9 @@
1
+ module LogStash; module PluginMixins; module ElasticSearch
2
+ class NoopLicenseChecker
3
+ INSTANCE = self.new
4
+
5
+ def appropriate_license?(pool, url)
6
+ true
7
+ end
8
+ end
9
+ end; end; end