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.
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