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.
- 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,282 @@
|
|
1
|
+
module LogStash module Outputs class ElasticSearch
|
2
|
+
# DS specific behavior/configuration.
|
3
|
+
module DataStreamSupport
|
4
|
+
|
5
|
+
# @api private
|
6
|
+
ENABLING_ECS_GUIDANCE = <<~END.tr("\n", " ")
|
7
|
+
Elasticsearch data streams require that events adhere to the Elastic Common Schema.
|
8
|
+
While `ecs_compatibility` can be set for this individual Elasticsearch output plugin, doing so will not fix schema conflicts caused by upstream plugins in your pipeline.
|
9
|
+
To avoid mapping conflicts, you will need to use ECS-compatible field names and datatypes throughout your pipeline.
|
10
|
+
Many plugins support an `ecs_compatibility` mode, and the `pipeline.ecs_compatibility` setting can be used to opt-in for all plugins in a pipeline.
|
11
|
+
END
|
12
|
+
private_constant :ENABLING_ECS_GUIDANCE
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
# Defines whether data will be indexed into an Elasticsearch data stream,
|
16
|
+
# `data_stream_*` settings will only be used if this setting is enabled!
|
17
|
+
# This setting supports values `true`, `false`, and `auto`.
|
18
|
+
# Defaults to `false` in Logstash 7.x and `auto` starting in Logstash 8.0.
|
19
|
+
base.config :data_stream, :validate => ['true', 'false', 'auto']
|
20
|
+
|
21
|
+
base.config :data_stream_type, :validate => ['logs', 'metrics', 'synthetics', 'traces'], :default => 'logs'
|
22
|
+
base.config :data_stream_dataset, :validate => :dataset_identifier, :default => 'generic'
|
23
|
+
base.config :data_stream_namespace, :validate => :namespace_identifier, :default => 'default'
|
24
|
+
|
25
|
+
base.config :data_stream_sync_fields, :validate => :boolean, :default => true
|
26
|
+
base.config :data_stream_auto_routing, :validate => :boolean, :default => true
|
27
|
+
|
28
|
+
base.extend(Validator)
|
29
|
+
end
|
30
|
+
|
31
|
+
def data_stream_config?
|
32
|
+
@data_stream_config.nil? ? @data_stream_config = check_data_stream_config! : @data_stream_config
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def data_stream_name(event)
|
38
|
+
data_stream = event.get('data_stream')
|
39
|
+
return @index if !data_stream_auto_routing || !data_stream.is_a?(Hash)
|
40
|
+
|
41
|
+
type = data_stream['type'] || data_stream_type
|
42
|
+
dataset = data_stream['dataset'] || data_stream_dataset
|
43
|
+
namespace = data_stream['namespace'] || data_stream_namespace
|
44
|
+
"#{type}-#{dataset}-#{namespace}"
|
45
|
+
end
|
46
|
+
|
47
|
+
DATA_STREAMS_AND_ECS_ENABLED_BY_DEFAULT_LS_VERSION = '8.0.0'
|
48
|
+
|
49
|
+
# @param params the user configuration for the ES output
|
50
|
+
# @note LS initialized configuration (with filled defaults) won't detect as data-stream
|
51
|
+
# compatible, only explicit (`original_params`) config should be tested.
|
52
|
+
# @return [Boolean] whether given configuration is data-stream compatible
|
53
|
+
def check_data_stream_config!(params = original_params)
|
54
|
+
case data_stream_explicit_value
|
55
|
+
when false
|
56
|
+
check_disabled_data_stream_config!(params)
|
57
|
+
return false
|
58
|
+
when true
|
59
|
+
check_enabled_data_stream_config!(params)
|
60
|
+
return true
|
61
|
+
else # data_stream => auto or not set
|
62
|
+
use_data_stream = data_stream_default(params)
|
63
|
+
|
64
|
+
check_disabled_data_stream_config!(params) unless use_data_stream
|
65
|
+
|
66
|
+
@logger.info("Data streams auto configuration (`data_stream => auto` or unset) resolved to `#{use_data_stream}`")
|
67
|
+
return use_data_stream
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def check_enabled_data_stream_config!(params)
|
72
|
+
invalid_data_stream_params = invalid_data_stream_params(params)
|
73
|
+
|
74
|
+
if invalid_data_stream_params.any?
|
75
|
+
@logger.error "Invalid data stream configuration, the following parameters are not supported:", invalid_data_stream_params
|
76
|
+
raise LogStash::ConfigurationError, "Invalid data stream configuration: #{invalid_data_stream_params.keys}"
|
77
|
+
end
|
78
|
+
|
79
|
+
if ecs_compatibility == :disabled
|
80
|
+
if ecs_compatibility_required?
|
81
|
+
@logger.error "Invalid data stream configuration; `ecs_compatibility` must not be `disabled`. " + ENABLING_ECS_GUIDANCE
|
82
|
+
raise LogStash::ConfigurationError, "Invalid data stream configuration: `ecs_compatibility => disabled`"
|
83
|
+
end
|
84
|
+
|
85
|
+
@deprecation_logger.deprecated "In a future release of Logstash, the Elasticsearch output plugin's `data_stream => true` will require the plugin to be run in ECS compatibility mode. " + ENABLING_ECS_GUIDANCE
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def check_disabled_data_stream_config!(params)
|
90
|
+
data_stream_params = data_stream_params(params)
|
91
|
+
|
92
|
+
if data_stream_params.any?
|
93
|
+
@logger.error "Ambiguous configuration; data stream settings must not be present when data streams are disabled (caused by `data_stream => false`, `data_stream => auto` or unset resolved to false). " \
|
94
|
+
"You can either manually set `data_stream => true` or remove the following specific data stream settings: ", data_stream_params
|
95
|
+
|
96
|
+
raise LogStash::ConfigurationError,
|
97
|
+
"Ambiguous configuration; data stream settings must not be present when data streams are disabled: #{data_stream_params.keys}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def data_stream_params(params)
|
102
|
+
params.select { |name, _| name.start_with?('data_stream_') }
|
103
|
+
end
|
104
|
+
|
105
|
+
def data_stream_explicit_value
|
106
|
+
case @data_stream
|
107
|
+
when 'true'
|
108
|
+
return true
|
109
|
+
when 'false'
|
110
|
+
return false
|
111
|
+
else
|
112
|
+
return nil # 'auto' or not set by user
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def invalid_data_stream_params(params)
|
117
|
+
shared_params = LogStash::PluginMixins::ElasticSearch::APIConfigs::CONFIG_PARAMS.keys.map(&:to_s)
|
118
|
+
params.reject do |name, value|
|
119
|
+
# NOTE: intentionally do not support explicit DS configuration like:
|
120
|
+
# - `index => ...` identifier provided by data_stream_xxx settings
|
121
|
+
case name
|
122
|
+
when 'action'
|
123
|
+
value == 'create'
|
124
|
+
when 'routing', 'pipeline'
|
125
|
+
true
|
126
|
+
when 'data_stream'
|
127
|
+
value.to_s == 'true'
|
128
|
+
when 'manage_template'
|
129
|
+
value.to_s == 'false'
|
130
|
+
when 'ecs_compatibility' then true # required for LS <= 6.x
|
131
|
+
else
|
132
|
+
name.start_with?('data_stream_') ||
|
133
|
+
shared_params.include?(name) ||
|
134
|
+
inherited_internal_config_param?(name) # 'id', 'enabled_metric' etc
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def inherited_internal_config_param?(name)
|
140
|
+
self.class.superclass.get_config.key?(name.to_s) # superclass -> LogStash::Outputs::Base
|
141
|
+
end
|
142
|
+
|
143
|
+
DATA_STREAMS_ORIGIN_ES_VERSION = '7.9.0'
|
144
|
+
|
145
|
+
# @note assumes to be running AFTER {after_successful_connection} completed, due ES version checks
|
146
|
+
# @return [Gem::Version] if ES supports DS nil (or raise) otherwise
|
147
|
+
def assert_es_version_supports_data_streams
|
148
|
+
fail 'no last_es_version' unless last_es_version # assert - should not happen
|
149
|
+
es_version = ::Gem::Version.create(last_es_version)
|
150
|
+
if es_version < ::Gem::Version.create(DATA_STREAMS_ORIGIN_ES_VERSION)
|
151
|
+
@logger.error "Elasticsearch version does not support data streams, Logstash might end up writing to an index", es_version: es_version.version
|
152
|
+
# NOTE: when switching to synchronous check from register, this should be a ConfigurationError
|
153
|
+
raise LogStash::Error, "A data_stream configuration is only supported since Elasticsearch #{DATA_STREAMS_ORIGIN_ES_VERSION} " +
|
154
|
+
"(detected version #{es_version.version}), please upgrade your cluster"
|
155
|
+
end
|
156
|
+
es_version # return truthy
|
157
|
+
end
|
158
|
+
|
159
|
+
# when data_stream => is either 'auto' or not set
|
160
|
+
def data_stream_default(params)
|
161
|
+
if ecs_compatibility == :disabled
|
162
|
+
@logger.info("Not eligible for data streams because ecs_compatibility is not enabled. " + ENABLING_ECS_GUIDANCE)
|
163
|
+
return false
|
164
|
+
end
|
165
|
+
|
166
|
+
invalid_data_stream_params = invalid_data_stream_params(params)
|
167
|
+
|
168
|
+
if data_stream_and_ecs_enabled_by_default?
|
169
|
+
if invalid_data_stream_params.any?
|
170
|
+
@logger.info("Not eligible for data streams because config contains one or more settings that are not compatible with data streams: #{invalid_data_stream_params.inspect}")
|
171
|
+
return false
|
172
|
+
end
|
173
|
+
|
174
|
+
return true
|
175
|
+
end
|
176
|
+
|
177
|
+
# LS 7.x
|
178
|
+
if !invalid_data_stream_params.any? && !data_stream_params(params).any?
|
179
|
+
@logger.warn "Configuration is data stream compliant but due backwards compatibility Logstash 7.x will not assume " +
|
180
|
+
"writing to a data-stream, default behavior will change on Logstash 8.0 " +
|
181
|
+
"(set `data_stream => true/false` to disable this warning)"
|
182
|
+
end
|
183
|
+
false
|
184
|
+
end
|
185
|
+
|
186
|
+
def ecs_compatibility_required?
|
187
|
+
data_stream_and_ecs_enabled_by_default?
|
188
|
+
end
|
189
|
+
|
190
|
+
def data_stream_and_ecs_enabled_by_default?
|
191
|
+
::Gem::Version.create(LOGSTASH_VERSION) >= ::Gem::Version.create(DATA_STREAMS_AND_ECS_ENABLED_BY_DEFAULT_LS_VERSION)
|
192
|
+
end
|
193
|
+
|
194
|
+
# an {event_action_tuple} replacement when a data-stream configuration is detected
|
195
|
+
def data_stream_event_action_tuple(event)
|
196
|
+
event_data = event.to_hash
|
197
|
+
data_stream_event_sync(event_data) if data_stream_sync_fields
|
198
|
+
EventActionTuple.new('create', common_event_params(event), event, event_data)
|
199
|
+
end
|
200
|
+
|
201
|
+
DATA_STREAM_SYNC_FIELDS = [ 'type', 'dataset', 'namespace' ].freeze
|
202
|
+
|
203
|
+
def data_stream_event_sync(event_data)
|
204
|
+
data_stream = event_data['data_stream']
|
205
|
+
if data_stream.is_a?(Hash)
|
206
|
+
unless data_stream_auto_routing
|
207
|
+
sync_fields = DATA_STREAM_SYNC_FIELDS.select { |name| data_stream.key?(name) && data_stream[name] != send(:"data_stream_#{name}") }
|
208
|
+
if sync_fields.any? # these fields will need to be overwritten
|
209
|
+
info = sync_fields.inject({}) { |info, name| info[name] = data_stream[name]; info }
|
210
|
+
info[:event] = event_data
|
211
|
+
@logger.warn "Some data_stream fields are out of sync, these will be updated to reflect data-stream name", info
|
212
|
+
|
213
|
+
# NOTE: we work directly with event.to_hash data thus fine to mutate the 'data_stream' hash
|
214
|
+
sync_fields.each { |name| data_stream[name] = nil } # fallback to ||= bellow
|
215
|
+
end
|
216
|
+
end
|
217
|
+
else
|
218
|
+
unless data_stream.nil?
|
219
|
+
@logger.warn "Invalid 'data_stream' field type, due fields sync will overwrite", value: data_stream, event: event_data
|
220
|
+
end
|
221
|
+
event_data['data_stream'] = data_stream = Hash.new
|
222
|
+
end
|
223
|
+
|
224
|
+
data_stream['type'] ||= data_stream_type
|
225
|
+
data_stream['dataset'] ||= data_stream_dataset
|
226
|
+
data_stream['namespace'] ||= data_stream_namespace
|
227
|
+
|
228
|
+
event_data
|
229
|
+
end
|
230
|
+
|
231
|
+
module Validator
|
232
|
+
|
233
|
+
# @override {LogStash::Config::Mixin::validate_value} to handle custom validators
|
234
|
+
# @param value [Array<Object>]
|
235
|
+
# @param validator [nil,Array,Symbol]
|
236
|
+
# @return [Array(true,Object)]: if validation is a success, a tuple containing `true` and the coerced value
|
237
|
+
# @return [Array(false,String)]: if validation is a failure, a tuple containing `false` and the failure reason.
|
238
|
+
def validate_value(value, validator)
|
239
|
+
case validator
|
240
|
+
when :dataset_identifier then validate_dataset_identifier(value)
|
241
|
+
when :namespace_identifier then validate_namespace_identifier(value)
|
242
|
+
else super
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
private
|
247
|
+
|
248
|
+
def validate_dataset_identifier(value)
|
249
|
+
valid, value = validate_value(value, :string)
|
250
|
+
return false, value unless valid
|
251
|
+
|
252
|
+
validate_identifier(value)
|
253
|
+
end
|
254
|
+
|
255
|
+
def validate_namespace_identifier(value)
|
256
|
+
valid, value = validate_value(value, :string)
|
257
|
+
return false, value unless valid
|
258
|
+
|
259
|
+
validate_identifier(value)
|
260
|
+
end
|
261
|
+
|
262
|
+
def validate_identifier(value, max_size = 100)
|
263
|
+
if value.empty?
|
264
|
+
return false, "Invalid identifier - empty string"
|
265
|
+
end
|
266
|
+
if value.bytesize > max_size
|
267
|
+
return false, "Invalid identifier - too long (#{value.bytesize} bytes)"
|
268
|
+
end
|
269
|
+
# cannot include \, /, *, ?, ", <, >, |, ' ' (space char), ',', #, :
|
270
|
+
if value.match? Regexp.union(INVALID_IDENTIFIER_CHARS)
|
271
|
+
return false, "Invalid characters detected #{INVALID_IDENTIFIER_CHARS.inspect} are not allowed"
|
272
|
+
end
|
273
|
+
return true, value
|
274
|
+
end
|
275
|
+
|
276
|
+
INVALID_IDENTIFIER_CHARS = [ '\\', '/', '*', '?', '"', '<', '>', '|', ' ', ',', '#', ':' ]
|
277
|
+
private_constant :INVALID_IDENTIFIER_CHARS
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
end end end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'manticore'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module LogStash; module Outputs; class ElasticSearch; class HttpClient;
|
5
|
+
DEFAULT_HEADERS = { "Content-Type" => "application/json" }
|
6
|
+
|
7
|
+
class ManticoreAdapter
|
8
|
+
attr_reader :manticore, :logger
|
9
|
+
|
10
|
+
def initialize(logger, options)
|
11
|
+
@logger = logger
|
12
|
+
options = options.dup
|
13
|
+
options[:ssl] = options[:ssl] || {}
|
14
|
+
|
15
|
+
# We manage our own retries directly, so let's disable them here
|
16
|
+
options[:automatic_retries] = 0
|
17
|
+
# We definitely don't need cookies
|
18
|
+
options[:cookies] = false
|
19
|
+
|
20
|
+
@client_params = {:headers => DEFAULT_HEADERS.merge(options[:headers] || {})}
|
21
|
+
|
22
|
+
if options[:proxy]
|
23
|
+
options[:proxy] = manticore_proxy_hash(options[:proxy])
|
24
|
+
end
|
25
|
+
|
26
|
+
@manticore = ::Manticore::Client.new(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Transform the proxy option to a hash. Manticore's support for non-hash
|
30
|
+
# proxy options is broken. This was fixed in https://github.com/cheald/manticore/commit/34a00cee57a56148629ed0a47c329181e7319af5
|
31
|
+
# but this is not yet released
|
32
|
+
def manticore_proxy_hash(proxy_uri)
|
33
|
+
[:scheme, :port, :user, :password, :path].reduce(:host => proxy_uri.host) do |acc,opt|
|
34
|
+
value = proxy_uri.send(opt)
|
35
|
+
acc[opt] = value unless value.nil? || (value.is_a?(String) && value.empty?)
|
36
|
+
acc
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def client
|
41
|
+
@manticore
|
42
|
+
end
|
43
|
+
|
44
|
+
# Performs the request by invoking {Transport::Base#perform_request} with a block.
|
45
|
+
#
|
46
|
+
# @return [Response]
|
47
|
+
# @see Transport::Base#perform_request
|
48
|
+
#
|
49
|
+
def perform_request(url, method, path, params={}, body=nil)
|
50
|
+
# Perform 2-level deep merge on the params, so if the passed params and client params will both have hashes stored on a key they
|
51
|
+
# will be merged as well, instead of choosing just one of the values
|
52
|
+
params = (params || {}).merge(@client_params) { |key, oldval, newval|
|
53
|
+
(oldval.is_a?(Hash) && newval.is_a?(Hash)) ? oldval.merge(newval) : newval
|
54
|
+
}
|
55
|
+
params[:body] = body if body
|
56
|
+
|
57
|
+
if url.user
|
58
|
+
params[:auth] = {
|
59
|
+
:user => CGI.unescape(url.user),
|
60
|
+
# We have to unescape the password here since manticore won't do it
|
61
|
+
# for us unless its part of the URL
|
62
|
+
:password => CGI.unescape(url.password),
|
63
|
+
:eager => true
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
request_uri = format_url(url, path)
|
68
|
+
request_uri_as_string = remove_double_escaping(request_uri.to_s)
|
69
|
+
begin
|
70
|
+
resp = @manticore.send(method.downcase, request_uri_as_string, params)
|
71
|
+
# Manticore returns lazy responses by default
|
72
|
+
# We want to block for our usage, this will wait for the response to finish
|
73
|
+
resp.call
|
74
|
+
rescue ::Manticore::ManticoreException => e
|
75
|
+
log_request_error(e)
|
76
|
+
raise ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError.new(e, request_uri_as_string)
|
77
|
+
end
|
78
|
+
|
79
|
+
# 404s are excluded because they are valid codes in the case of
|
80
|
+
# template installation. We might need a better story around this later
|
81
|
+
# but for our current purposes this is correct
|
82
|
+
code = resp.code
|
83
|
+
if code < 200 || code > 299 && code != 404
|
84
|
+
raise ::LogStash::Outputs::ElasticSearch::HttpClient::Pool::BadResponseCodeError.new(code, request_uri, body, resp.body)
|
85
|
+
end
|
86
|
+
|
87
|
+
resp
|
88
|
+
end
|
89
|
+
|
90
|
+
def log_request_error(e)
|
91
|
+
details = { message: e.message, exception: e.class }
|
92
|
+
details[:cause] = e.cause if e.respond_to?(:cause)
|
93
|
+
details[:backtrace] = e.backtrace if @logger.debug?
|
94
|
+
|
95
|
+
level = case e
|
96
|
+
when ::Manticore::Timeout
|
97
|
+
:debug
|
98
|
+
when ::Manticore::UnknownException
|
99
|
+
:warn
|
100
|
+
else
|
101
|
+
:info
|
102
|
+
end
|
103
|
+
|
104
|
+
@logger.send level, "Failed to perform request", details
|
105
|
+
log_java_exception(details[:cause], :debug) if details[:cause] && @logger.debug?
|
106
|
+
end
|
107
|
+
|
108
|
+
def log_java_exception(e, level = :debug)
|
109
|
+
return unless e.is_a?(java.lang.Exception)
|
110
|
+
# @logger.name using the same convention as LS does
|
111
|
+
logger = self.class.name.gsub('::', '.').downcase
|
112
|
+
logger = org.apache.logging.log4j.LogManager.getLogger(logger)
|
113
|
+
logger.send(level, '', e) # logger.error('', e) - prints nested causes
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returned urls from this method should be checked for double escaping.
|
117
|
+
def format_url(url, path_and_query=nil)
|
118
|
+
request_uri = url.clone
|
119
|
+
|
120
|
+
# We excise auth info from the URL in case manticore itself tries to stick
|
121
|
+
# sensitive data in a thrown exception or log data
|
122
|
+
request_uri.user = nil
|
123
|
+
request_uri.password = nil
|
124
|
+
|
125
|
+
return request_uri.to_s if path_and_query.nil?
|
126
|
+
|
127
|
+
parsed_path_and_query = java.net.URI.new(path_and_query)
|
128
|
+
|
129
|
+
new_query_parts = [request_uri.query, parsed_path_and_query.query].select do |part|
|
130
|
+
part && !part.empty? # Skip empty nil and ""
|
131
|
+
end
|
132
|
+
|
133
|
+
request_uri.query = new_query_parts.join("&") unless new_query_parts.empty?
|
134
|
+
|
135
|
+
# use `raw_path`` as `path` will unescape any escaped '/' in the path
|
136
|
+
request_uri.path = "#{request_uri.path}/#{parsed_path_and_query.raw_path}".gsub(/\/{2,}/, "/")
|
137
|
+
request_uri
|
138
|
+
end
|
139
|
+
|
140
|
+
# Later versions of SafeURI will also escape the '%' sign in an already escaped URI.
|
141
|
+
# (If the path variable is used, it constructs a new java.net.URI object using the multi-arg constructor,
|
142
|
+
# which will escape any '%' characters in the path, as opposed to the single-arg constructor which requires illegal
|
143
|
+
# characters to be already escaped, and will throw otherwise)
|
144
|
+
# The URI needs to have been previously escaped, as it does not play nice with an escaped '/' in the
|
145
|
+
# middle of a URI, as required by date math, treating it as a path separator
|
146
|
+
def remove_double_escaping(url)
|
147
|
+
url.gsub(/%25([0-9A-F]{2})/i, '%\1')
|
148
|
+
end
|
149
|
+
|
150
|
+
def close
|
151
|
+
@manticore.close
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end; end; end; end
|