logstash-output-opensearch 1.0.0-java
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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ADMINS.md +29 -0
- data/CODE_OF_CONDUCT.md +25 -0
- data/CONTRIBUTING.md +99 -0
- data/DEVELOPER_GUIDE.md +208 -0
- data/Gemfile +20 -0
- data/LICENSE +202 -0
- data/MAINTAINERS.md +71 -0
- data/NOTICE +2 -0
- data/README.md +37 -0
- data/RELEASING.md +36 -0
- data/SECURITY.md +3 -0
- data/lib/logstash/outputs/opensearch.rb +449 -0
- data/lib/logstash/outputs/opensearch/distribution_checker.rb +44 -0
- data/lib/logstash/outputs/opensearch/http_client.rb +465 -0
- data/lib/logstash/outputs/opensearch/http_client/manticore_adapter.rb +140 -0
- data/lib/logstash/outputs/opensearch/http_client/pool.rb +467 -0
- data/lib/logstash/outputs/opensearch/http_client_builder.rb +182 -0
- data/lib/logstash/outputs/opensearch/template_manager.rb +60 -0
- data/lib/logstash/outputs/opensearch/templates/ecs-disabled/1x.json +44 -0
- data/lib/logstash/outputs/opensearch/templates/ecs-disabled/7x.json +44 -0
- data/lib/logstash/plugin_mixins/opensearch/api_configs.rb +168 -0
- data/lib/logstash/plugin_mixins/opensearch/common.rb +294 -0
- data/lib/logstash/plugin_mixins/opensearch/noop_distribution_checker.rb +18 -0
- data/logstash-output-opensearch.gemspec +40 -0
- data/spec/fixtures/_nodes/nodes.json +74 -0
- data/spec/fixtures/htpasswd +2 -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/integration/outputs/compressed_indexing_spec.rb +76 -0
- data/spec/integration/outputs/create_spec.rb +76 -0
- data/spec/integration/outputs/delete_spec.rb +72 -0
- data/spec/integration/outputs/index_spec.rb +164 -0
- data/spec/integration/outputs/index_version_spec.rb +110 -0
- data/spec/integration/outputs/ingest_pipeline_spec.rb +82 -0
- data/spec/integration/outputs/metrics_spec.rb +75 -0
- data/spec/integration/outputs/no_opensearch_on_startup_spec.rb +67 -0
- data/spec/integration/outputs/painless_update_spec.rb +147 -0
- data/spec/integration/outputs/parent_spec.rb +103 -0
- data/spec/integration/outputs/retry_spec.rb +182 -0
- data/spec/integration/outputs/routing_spec.rb +70 -0
- data/spec/integration/outputs/sniffer_spec.rb +70 -0
- data/spec/integration/outputs/templates_spec.rb +105 -0
- data/spec/integration/outputs/update_spec.rb +123 -0
- data/spec/opensearch_spec_helper.rb +141 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/unit/http_client_builder_spec.rb +194 -0
- data/spec/unit/outputs/error_whitelist_spec.rb +62 -0
- data/spec/unit/outputs/opensearch/http_client/manticore_adapter_spec.rb +159 -0
- data/spec/unit/outputs/opensearch/http_client/pool_spec.rb +306 -0
- data/spec/unit/outputs/opensearch/http_client_spec.rb +292 -0
- data/spec/unit/outputs/opensearch/template_manager_spec.rb +36 -0
- data/spec/unit/outputs/opensearch_proxy_spec.rb +112 -0
- data/spec/unit/outputs/opensearch_spec.rb +800 -0
- data/spec/unit/outputs/opensearch_ssl_spec.rb +179 -0
- metadata +289 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
#
|
3
|
+
# The OpenSearch Contributors require contributions made to
|
4
|
+
# this file be licensed under the Apache-2.0 license or a
|
5
|
+
# compatible open source license.
|
6
|
+
#
|
7
|
+
# Modifications Copyright OpenSearch Contributors. See
|
8
|
+
# GitHub history for details.
|
9
|
+
|
10
|
+
require "logstash/devutils/rspec/spec_helper"
|
11
|
+
require "logstash/outputs/opensearch/template_manager"
|
12
|
+
|
13
|
+
describe LogStash::Outputs::OpenSearch::TemplateManager do
|
14
|
+
|
15
|
+
describe ".default_template_path" do
|
16
|
+
context 'when ECS v1 is requested' do
|
17
|
+
it 'resolves' do
|
18
|
+
expect(described_class.default_template_path(7, :v1)).to end_with("/templates/ecs-v1/7x.json")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "index template settings" do
|
24
|
+
let(:plugin_settings) { {"manage_template" => true, "template_overwrite" => true} }
|
25
|
+
let(:plugin) { LogStash::Outputs::OpenSearch.new(plugin_settings) }
|
26
|
+
|
27
|
+
describe "use template api" do
|
28
|
+
let(:file_path) { described_class.default_template_path(7) }
|
29
|
+
let(:template) { described_class.read_template_file(file_path)}
|
30
|
+
|
31
|
+
it "should update settings" do
|
32
|
+
expect(template.include?('template')).to be_falsey
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
#
|
3
|
+
# The OpenSearch Contributors require contributions made to
|
4
|
+
# this file be licensed under the Apache-2.0 license or a
|
5
|
+
# compatible open source license.
|
6
|
+
#
|
7
|
+
# Modifications Copyright OpenSearch Contributors. See
|
8
|
+
# GitHub history for details.
|
9
|
+
|
10
|
+
require_relative "../../../spec/spec_helper"
|
11
|
+
require 'stud/temporary'
|
12
|
+
require 'manticore/client'
|
13
|
+
|
14
|
+
describe "Proxy option" do
|
15
|
+
let(:settings) { { "hosts" => "node01" } }
|
16
|
+
subject {
|
17
|
+
LogStash::Outputs::OpenSearch.new(settings)
|
18
|
+
}
|
19
|
+
|
20
|
+
before do
|
21
|
+
allow(::Manticore::Client).to receive(:new).with(any_args).and_call_original
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "valid configs" do
|
25
|
+
before do
|
26
|
+
subject.register
|
27
|
+
end
|
28
|
+
|
29
|
+
after do
|
30
|
+
subject.close
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when specified as a URI" do
|
34
|
+
shared_examples("hash conversion") do |hash|
|
35
|
+
let(:settings) { super().merge("proxy" => proxy)}
|
36
|
+
|
37
|
+
it "should set the proxy to the correct hash value" do
|
38
|
+
expect(::Manticore::Client).to have_received(:new) do |options|
|
39
|
+
expect(options[:proxy]).to eq(hash)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "simple proxy" do
|
45
|
+
let(:proxy) { LogStash::Util::SafeURI.new("http://127.0.0.1:1234") }
|
46
|
+
|
47
|
+
include_examples("hash conversion",
|
48
|
+
{
|
49
|
+
:host => "127.0.0.1",
|
50
|
+
:scheme => "http",
|
51
|
+
:port => 1234
|
52
|
+
}
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
describe "a secure authed proxy" do
|
58
|
+
let(:proxy) { LogStash::Util::SafeURI.new("https://myuser:mypass@127.0.0.1:1234") }
|
59
|
+
|
60
|
+
include_examples("hash conversion",
|
61
|
+
{
|
62
|
+
:host => "127.0.0.1",
|
63
|
+
:scheme => "https",
|
64
|
+
:user => "myuser",
|
65
|
+
:password => "mypass",
|
66
|
+
:port => 1234
|
67
|
+
}
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when not specified" do
|
73
|
+
it "should not send the proxy option to manticore" do
|
74
|
+
expect(::Manticore::Client).to have_received(:new) do |options|
|
75
|
+
expect(options).not_to include(:proxy)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "when specified as ''" do
|
82
|
+
let(:settings) { super().merge("proxy" => "${A_MISSING_ENV_VARIABLE:}")}
|
83
|
+
|
84
|
+
it "should not send the proxy option to manticore" do
|
85
|
+
expect { subject.register }.not_to raise_error
|
86
|
+
|
87
|
+
expect(::Manticore::Client).to have_received(:new) do |options|
|
88
|
+
expect(options).not_to include(:proxy)
|
89
|
+
end
|
90
|
+
|
91
|
+
subject.close
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "when specified as invalid uri" do
|
96
|
+
let(:settings) { super().merge("proxy" => ":")}
|
97
|
+
|
98
|
+
it "should fail" do
|
99
|
+
# SafeURI isn't doing the proper exception wrapping for us, we can not simply :
|
100
|
+
# expect { subject.register }.to raise_error(ArgumentError, /URI is not valid/i)
|
101
|
+
begin
|
102
|
+
subject.register
|
103
|
+
rescue ArgumentError => e
|
104
|
+
expect(e.message).to match /URI is not valid/i
|
105
|
+
rescue java.net.URISyntaxException => e
|
106
|
+
expect(e.message).to match /scheme name/i
|
107
|
+
else
|
108
|
+
fail 'exception not raised'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,800 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
#
|
3
|
+
# The OpenSearch Contributors require contributions made to
|
4
|
+
# this file be licensed under the Apache-2.0 license or a
|
5
|
+
# compatible open source license.
|
6
|
+
#
|
7
|
+
# Modifications Copyright OpenSearch Contributors. See
|
8
|
+
# GitHub history for details.
|
9
|
+
|
10
|
+
require_relative "../../../spec/spec_helper"
|
11
|
+
require "base64"
|
12
|
+
require "flores/random"
|
13
|
+
require 'concurrent/atomic/count_down_latch'
|
14
|
+
require "logstash/outputs/opensearch"
|
15
|
+
|
16
|
+
describe LogStash::Outputs::OpenSearch do
|
17
|
+
subject(:opensearch_output_instance) { described_class.new(options) }
|
18
|
+
let(:options) { {} }
|
19
|
+
let(:maximum_seen_major_version) { [7].sample }
|
20
|
+
|
21
|
+
let(:do_register) { true }
|
22
|
+
|
23
|
+
let(:stub_http_client_pool!) do
|
24
|
+
allow_any_instance_of(LogStash::Outputs::OpenSearch::HttpClient::Pool).to receive(:start)
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:after_successful_connection_thread_mock) do
|
28
|
+
double('after_successful_connection_thread', value: true)
|
29
|
+
end
|
30
|
+
|
31
|
+
before(:each) do
|
32
|
+
if do_register
|
33
|
+
stub_http_client_pool!
|
34
|
+
|
35
|
+
allow(subject).to receive(:finish_register) # stub-out thread completion (to avoid error log entries)
|
36
|
+
|
37
|
+
# emulate 'successful' OpenSearch connection on the same thread
|
38
|
+
allow(subject).to receive(:after_successful_connection) { |&block| block.call }.
|
39
|
+
and_return after_successful_connection_thread_mock
|
40
|
+
allow(subject).to receive(:stop_after_successful_connection_thread)
|
41
|
+
|
42
|
+
subject.register
|
43
|
+
|
44
|
+
allow(subject.client).to receive(:maximum_seen_major_version).at_least(:once).and_return(maximum_seen_major_version)
|
45
|
+
|
46
|
+
subject.client.pool.adapter.manticore.respond_with(:body => "{}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
after(:each) do
|
51
|
+
subject.close
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
context "with an active instance" do
|
56
|
+
let(:options) {
|
57
|
+
{
|
58
|
+
"index" => "my-index",
|
59
|
+
"hosts" => ["localhost","localhost:9202"],
|
60
|
+
"path" => "some-path",
|
61
|
+
"manage_template" => false
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
let(:manticore_urls) { subject.client.pool.urls }
|
66
|
+
let(:manticore_url) { manticore_urls.first }
|
67
|
+
|
68
|
+
let(:stub_http_client_pool!) do
|
69
|
+
[:start_resurrectionist, :start_sniffer, :healthcheck!].each do |method|
|
70
|
+
allow_any_instance_of(LogStash::Outputs::OpenSearch::HttpClient::Pool).to receive(method)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "getting a document type" do
|
75
|
+
context "if document_type isn't set" do
|
76
|
+
let(:options) { super().merge("document_type" => nil)}
|
77
|
+
let(:maximum_seen_major_version) { 7 }
|
78
|
+
it "should return '_doc'" do
|
79
|
+
expect(subject.send(:get_event_type, LogStash::Event.new("type" => "foo"))).to eql("_doc")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "with 'document type set'" do
|
84
|
+
let(:options) { super().merge("document_type" => "bar")}
|
85
|
+
it "should get the event type from the 'document_type' setting" do
|
86
|
+
expect(subject.send(:get_event_type, LogStash::Event.new())).to eql("bar")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "building an event action tuple" do
|
92
|
+
let(:maximum_seen_major_version) { 7 }
|
93
|
+
it "should not include '_type' when 'document_type' is not explicitly defined" do
|
94
|
+
action_tuple = subject.send(:event_action_tuple, LogStash::Event.new("type" => "foo"))
|
95
|
+
action_params = action_tuple[1]
|
96
|
+
expect(action_params).not_to include(:_type => "_doc")
|
97
|
+
end
|
98
|
+
|
99
|
+
context "with 'document type set'" do
|
100
|
+
let(:options) { super().merge("document_type" => "bar")}
|
101
|
+
it "should get the event type from the 'document_type' setting" do
|
102
|
+
action_tuple = subject.send(:event_action_tuple, LogStash::Event.new("type" => "foo"))
|
103
|
+
action_params = action_tuple[1]
|
104
|
+
expect(action_params).to include(:_type => "bar")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "with auth" do
|
110
|
+
let(:user) { "myuser" }
|
111
|
+
let(:password) { ::LogStash::Util::Password.new("mypassword") }
|
112
|
+
|
113
|
+
shared_examples "an authenticated config" do
|
114
|
+
it "should set the URL auth correctly" do
|
115
|
+
expect(manticore_url.user).to eq user
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "as part of a URL" do
|
120
|
+
let(:options) {
|
121
|
+
super().merge("hosts" => ["http://#{user}:#{password.value}@localhost:9200"])
|
122
|
+
}
|
123
|
+
|
124
|
+
include_examples("an authenticated config")
|
125
|
+
end
|
126
|
+
|
127
|
+
context "as a hash option" do
|
128
|
+
let(:options) {
|
129
|
+
super().merge!(
|
130
|
+
"user" => user,
|
131
|
+
"password" => password
|
132
|
+
)
|
133
|
+
}
|
134
|
+
|
135
|
+
include_examples("an authenticated config")
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "with path" do
|
141
|
+
it "should properly create a URI with the path" do
|
142
|
+
expect(subject.path).to eql(options["path"])
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should properly set the path on the HTTP client adding slashes" do
|
146
|
+
expect(manticore_url.path).to eql("/" + options["path"] + "/")
|
147
|
+
end
|
148
|
+
|
149
|
+
context "with extra slashes" do
|
150
|
+
let(:path) { "/slashed-path/ "}
|
151
|
+
let(:options) { super().merge("path" => "/some-path/") }
|
152
|
+
|
153
|
+
it "should properly set the path on the HTTP client without adding slashes" do
|
154
|
+
expect(manticore_url.path).to eql(options["path"])
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "with a URI based path" do
|
159
|
+
let(:options) do
|
160
|
+
o = super()
|
161
|
+
o.delete("path")
|
162
|
+
o["hosts"] = ["http://localhost:9200/mypath/"]
|
163
|
+
o
|
164
|
+
end
|
165
|
+
let(:client_host_path) { manticore_url.path }
|
166
|
+
|
167
|
+
it "should initialize without error" do
|
168
|
+
expect { subject }.not_to raise_error
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should use the URI path" do
|
172
|
+
expect(client_host_path).to eql("/mypath/")
|
173
|
+
end
|
174
|
+
|
175
|
+
context "with a path option but no URL path" do
|
176
|
+
let(:options) do
|
177
|
+
o = super()
|
178
|
+
o["path"] = "/override/"
|
179
|
+
o["hosts"] = ["http://localhost:9200"]
|
180
|
+
o
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should initialize without error" do
|
184
|
+
expect { subject }.not_to raise_error
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should use the option path" do
|
188
|
+
expect(client_host_path).to eql("/override/")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# If you specify the path in two spots that is an error!
|
193
|
+
context "with a path option and a URL path" do
|
194
|
+
let(:do_register) { false } # Register will fail
|
195
|
+
let(:options) do
|
196
|
+
o = super()
|
197
|
+
o["path"] = "/override"
|
198
|
+
o["hosts"] = ["http://localhost:9200/mypath/"]
|
199
|
+
o
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should initialize with an error" do
|
203
|
+
expect { subject.register }.to raise_error(LogStash::ConfigurationError)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "without a port specified" do
|
210
|
+
let(:options) { super().merge('hosts' => 'localhost') }
|
211
|
+
it "should properly set the default port (9200) on the HTTP client" do
|
212
|
+
expect(manticore_url.port).to eql(9200)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
describe "with a port other than 9200 specified" do
|
216
|
+
let(:options) { super().merge('hosts' => 'localhost:9202') }
|
217
|
+
it "should properly set the specified port on the HTTP client" do
|
218
|
+
expect(manticore_url.port).to eql(9202)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "#multi_receive" do
|
223
|
+
let(:events) { [double("one"), double("two"), double("three")] }
|
224
|
+
let(:events_tuples) { [double("one t"), double("two t"), double("three t")] }
|
225
|
+
|
226
|
+
before do
|
227
|
+
allow(subject).to receive(:retrying_submit).with(anything)
|
228
|
+
events.each_with_index do |e,i|
|
229
|
+
allow(subject).to receive(:event_action_tuple).with(e).and_return(events_tuples[i])
|
230
|
+
end
|
231
|
+
subject.multi_receive(events)
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
context "429 errors" do
|
237
|
+
let(:event) { ::LogStash::Event.new("foo" => "bar") }
|
238
|
+
let(:error) do
|
239
|
+
::LogStash::Outputs::OpenSearch::HttpClient::Pool::BadResponseCodeError.new(
|
240
|
+
429, double("url").as_null_object, request_body, double("response body")
|
241
|
+
)
|
242
|
+
end
|
243
|
+
let(:logger) { double("logger").as_null_object }
|
244
|
+
let(:response) { { :errors => [], :items => [] } }
|
245
|
+
|
246
|
+
let(:request_body) { double(:request_body, :bytesize => 1023) }
|
247
|
+
|
248
|
+
before(:each) do
|
249
|
+
|
250
|
+
i = 0
|
251
|
+
bulk_param = [["index", anything, event.to_hash]]
|
252
|
+
|
253
|
+
allow(subject).to receive(:logger).and_return(logger)
|
254
|
+
|
255
|
+
# Fail the first time bulk is called, succeed the next time
|
256
|
+
allow(subject.client).to receive(:bulk).with(bulk_param) do
|
257
|
+
i += 1
|
258
|
+
if i == 1
|
259
|
+
raise error
|
260
|
+
end
|
261
|
+
end.and_return(response)
|
262
|
+
subject.multi_receive([event])
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should retry the 429 till it goes away" do
|
266
|
+
expect(subject.client).to have_received(:bulk).twice
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should log a debug message" do
|
270
|
+
expect(subject.logger).to have_received(:debug).with(/Encountered a retryable error/i, anything)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context "unexpected bulk response" do
|
275
|
+
let(:options) do
|
276
|
+
{ "hosts" => "127.0.0.1:9999", "index" => "%{foo}", "manage_template" => false }
|
277
|
+
end
|
278
|
+
|
279
|
+
let(:events) { [ ::LogStash::Event.new("foo" => "bar1"), ::LogStash::Event.new("foo" => "bar2") ] }
|
280
|
+
|
281
|
+
let(:bulk_response) do
|
282
|
+
# shouldn't really happen but we've seen this happen - here OpenSearch returns more items than were sent
|
283
|
+
{ "took"=>1, "ingest_took"=>9, "errors"=>true,
|
284
|
+
"items"=>[{"index"=>{"_index"=>"bar1", "_type"=>"_doc", "_id"=>nil, "status"=>500,
|
285
|
+
"error"=>{"type" => "illegal_state_exception",
|
286
|
+
"reason" => "pipeline with id [test-ingest] could not be loaded, caused by [OpenSearchParseException[Error updating pipeline with id [test-ingest]]; nested: OpenSearchException[java.lang.IllegalArgumentException: no enrich index exists for policy with name [test-metadata1]]; nested: IllegalArgumentException[no enrich index exists for policy with name [test-metadata1]];; OpenSearchException[java.lang.IllegalArgumentException: no enrich index exists for policy with name [test-metadata1]]; nested: IllegalArgumentException[no enrich index exists for policy with name [test-metadata1]];; java.lang.IllegalArgumentException: no enrich index exists for policy with name [test-metadata1]]"
|
287
|
+
}
|
288
|
+
}
|
289
|
+
},
|
290
|
+
# NOTE: this is an artificial success (usually everything fails with a 500) but even if some doc where
|
291
|
+
# to succeed due the unexpected reponse items we can not clearly identify which actions to retry ...
|
292
|
+
{"index"=>{"_index"=>"bar2", "_type"=>"_doc", "_id"=>nil, "status"=>201}},
|
293
|
+
{"index"=>{"_index"=>"bar2", "_type"=>"_doc", "_id"=>nil, "status"=>500,
|
294
|
+
"error"=>{"type" => "illegal_state_exception",
|
295
|
+
"reason" => "pipeline with id [test-ingest] could not be loaded, caused by [OpenSearchParseException[Error updating pipeline with id [test-ingest]]; nested: OpenSearchException[java.lang.IllegalArgumentException: no enrich index exists for policy with name [test-metadata1]];"
|
296
|
+
}
|
297
|
+
}
|
298
|
+
}]
|
299
|
+
}
|
300
|
+
end
|
301
|
+
|
302
|
+
before(:each) do
|
303
|
+
allow(subject.client).to receive(:bulk_send).with(instance_of(StringIO), instance_of(Array)) do |stream, actions|
|
304
|
+
expect( stream.string ).to include '"foo":"bar1"'
|
305
|
+
expect( stream.string ).to include '"foo":"bar2"'
|
306
|
+
end.and_return(bulk_response, {"errors"=>false}) # let's make it go away (second call) to not retry indefinitely
|
307
|
+
end
|
308
|
+
|
309
|
+
it "should retry submit" do
|
310
|
+
allow(subject.logger).to receive(:error).with(/Encountered an unexpected error/i, anything)
|
311
|
+
allow(subject.client).to receive(:bulk).and_call_original # track count
|
312
|
+
|
313
|
+
subject.multi_receive(events)
|
314
|
+
|
315
|
+
expect(subject.client).to have_received(:bulk).twice
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should log specific error message" do
|
319
|
+
expect(subject.logger).to receive(:error).with(/Encountered an unexpected error/i,
|
320
|
+
hash_including(:message => 'Sent 2 documents but OpenSearch returned 3 responses (likely a bug with _bulk endpoint)'))
|
321
|
+
|
322
|
+
subject.multi_receive(events)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
context '413 errors' do
|
328
|
+
let(:payload_size) { LogStash::Outputs::OpenSearch::TARGET_BULK_BYTES + 1024 }
|
329
|
+
let(:event) { ::LogStash::Event.new("message" => ("a" * payload_size ) ) }
|
330
|
+
|
331
|
+
let(:logger_stub) { double("logger").as_null_object }
|
332
|
+
|
333
|
+
before(:each) do
|
334
|
+
allow(opensearch_output_instance.client).to receive(:logger).and_return(logger_stub)
|
335
|
+
|
336
|
+
allow(opensearch_output_instance.client).to receive(:bulk).and_call_original
|
337
|
+
|
338
|
+
max_bytes = payload_size * 3 / 4 # ensure a failure first attempt
|
339
|
+
allow(opensearch_output_instance.client.pool).to receive(:post) do |path, params, body|
|
340
|
+
if body.length > max_bytes
|
341
|
+
max_bytes *= 2 # ensure a successful retry
|
342
|
+
double("Response", :code => 413, :body => "")
|
343
|
+
else
|
344
|
+
double("Response", :code => 200, :body => '{"errors":false,"items":[{"index":{"status":200,"result":"created"}}]}')
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'retries the 413 until it goes away' do
|
350
|
+
opensearch_output_instance.multi_receive([event])
|
351
|
+
|
352
|
+
expect(opensearch_output_instance.client).to have_received(:bulk).twice
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'logs about payload quantity and size' do
|
356
|
+
opensearch_output_instance.multi_receive([event])
|
357
|
+
|
358
|
+
expect(logger_stub).to have_received(:warn)
|
359
|
+
.with(a_string_matching(/413 Payload Too Large/),
|
360
|
+
hash_including(:action_count => 1, :content_length => a_value > 20_000_000))
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
context "with timeout set" do
|
365
|
+
let(:listener) { Flores::Random.tcp_listener }
|
366
|
+
let(:port) { listener[2] }
|
367
|
+
let(:options) do
|
368
|
+
{
|
369
|
+
"manage_template" => false,
|
370
|
+
"hosts" => "localhost:#{port}",
|
371
|
+
"timeout" => 0.1, # fast timeout
|
372
|
+
}
|
373
|
+
end
|
374
|
+
|
375
|
+
before do
|
376
|
+
# Expect a timeout to be logged.
|
377
|
+
expect(subject.logger).to receive(:error).with(/Attempted to send a bulk request/i, anything).at_least(:once)
|
378
|
+
expect(subject.client).to receive(:bulk).at_least(:twice).and_call_original
|
379
|
+
end
|
380
|
+
|
381
|
+
it "should fail after the timeout" do
|
382
|
+
#pending("This is tricky now that we do healthchecks on instantiation")
|
383
|
+
Thread.new { subject.multi_receive([LogStash::Event.new]) }
|
384
|
+
|
385
|
+
# Allow the timeout to occur
|
386
|
+
sleep 6
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
describe "the action option" do
|
391
|
+
|
392
|
+
context "with a sprintf action" do
|
393
|
+
let(:options) { {"action" => "%{myactionfield}" } }
|
394
|
+
|
395
|
+
let(:event) { LogStash::Event.new("myactionfield" => "update", "message" => "blah") }
|
396
|
+
|
397
|
+
it "should interpolate the requested action value when creating an event_action_tuple" do
|
398
|
+
expect(subject.send(:event_action_tuple, event).first).to eql("update")
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
context "with a sprintf action equals to update" do
|
403
|
+
let(:options) { {"action" => "%{myactionfield}", "upsert" => '{"message": "some text"}' } }
|
404
|
+
|
405
|
+
let(:event) { LogStash::Event.new("myactionfield" => "update", "message" => "blah") }
|
406
|
+
|
407
|
+
it "should obtain specific action's params from event_action_tuple" do
|
408
|
+
expect(subject.send(:event_action_tuple, event)[1]).to include(:_upsert)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
context "with an invalid action" do
|
413
|
+
let(:options) { {"action" => "SOME Garbaaage"} }
|
414
|
+
let(:do_register) { false } # this is what we want to test, so we disable the before(:each) call
|
415
|
+
|
416
|
+
before { allow(subject).to receive(:finish_register) }
|
417
|
+
|
418
|
+
it "should raise a configuration error" do
|
419
|
+
expect { subject.register }.to raise_error(LogStash::ConfigurationError)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
describe "the pipeline option" do
|
425
|
+
|
426
|
+
context "with a sprintf and set pipeline" do
|
427
|
+
let(:options) { {"pipeline" => "%{pipeline}" } }
|
428
|
+
|
429
|
+
let(:event) { LogStash::Event.new("pipeline" => "my-ingest-pipeline") }
|
430
|
+
|
431
|
+
it "should interpolate the pipeline value and set it" do
|
432
|
+
expect(subject.send(:event_action_tuple, event)[1]).to include(:pipeline => "my-ingest-pipeline")
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
context "with a sprintf and empty pipeline" do
|
437
|
+
let(:options) { {"pipeline" => "%{pipeline}" } }
|
438
|
+
|
439
|
+
let(:event) { LogStash::Event.new("pipeline" => "") }
|
440
|
+
|
441
|
+
it "should interpolate the pipeline value but not set it because it is empty" do
|
442
|
+
expect(subject.send(:event_action_tuple, event)[1]).not_to include(:pipeline)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
describe "SSL end to end" do
|
448
|
+
let(:do_register) { false } # skip the register in the global before block, as is called here.
|
449
|
+
|
450
|
+
before(:each) do
|
451
|
+
stub_manticore_client!
|
452
|
+
subject.register
|
453
|
+
end
|
454
|
+
|
455
|
+
shared_examples("an encrypted client connection") do
|
456
|
+
it "should enable SSL in manticore" do
|
457
|
+
expect(subject.client.pool.urls.map(&:scheme).uniq).to eql(['https'])
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
|
462
|
+
context "With the 'ssl' option" do
|
463
|
+
let(:options) { {"ssl" => true}}
|
464
|
+
|
465
|
+
include_examples("an encrypted client connection")
|
466
|
+
end
|
467
|
+
|
468
|
+
context "With an https host" do
|
469
|
+
let(:options) { {"hosts" => "https://localhost"} }
|
470
|
+
include_examples("an encrypted client connection")
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
describe "retry_on_conflict" do
|
475
|
+
let(:num_retries) { 123 }
|
476
|
+
let(:event) { LogStash::Event.new("myactionfield" => "update", "message" => "blah") }
|
477
|
+
let(:options) { { 'retry_on_conflict' => num_retries } }
|
478
|
+
|
479
|
+
context "with a regular index" do
|
480
|
+
let(:options) { super().merge("action" => "index") }
|
481
|
+
|
482
|
+
it "should not set the retry_on_conflict parameter when creating an event_action_tuple" do
|
483
|
+
allow(subject.client).to receive(:maximum_seen_major_version).and_return(maximum_seen_major_version)
|
484
|
+
action, params, event_data = subject.send(:event_action_tuple, event)
|
485
|
+
expect(params).not_to include({subject.send(:retry_on_conflict_action_name) => num_retries})
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
context "using a plain update" do
|
490
|
+
let(:options) { super().merge("action" => "update", "retry_on_conflict" => num_retries, "document_id" => 1) }
|
491
|
+
|
492
|
+
it "should set the retry_on_conflict parameter when creating an event_action_tuple" do
|
493
|
+
action, params, event_data = subject.send(:event_action_tuple, event)
|
494
|
+
expect(params).to include({subject.send(:retry_on_conflict_action_name) => num_retries})
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
context "with a sprintf action that resolves to update" do
|
499
|
+
let(:options) { super().merge("action" => "%{myactionfield}", "retry_on_conflict" => num_retries, "document_id" => 1) }
|
500
|
+
|
501
|
+
it "should set the retry_on_conflict parameter when creating an event_action_tuple" do
|
502
|
+
action, params, event_data = subject.send(:event_action_tuple, event)
|
503
|
+
expect(params).to include({subject.send(:retry_on_conflict_action_name) => num_retries})
|
504
|
+
expect(action).to eq("update")
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
describe "sleep interval calculation" do
|
510
|
+
let(:retry_max_interval) { 64 }
|
511
|
+
let(:options) { { "retry_max_interval" => retry_max_interval } }
|
512
|
+
|
513
|
+
it "should double the given value" do
|
514
|
+
expect(subject.next_sleep_interval(2)).to eql(4)
|
515
|
+
expect(subject.next_sleep_interval(32)).to eql(64)
|
516
|
+
end
|
517
|
+
|
518
|
+
it "should not increase the value past the max retry interval" do
|
519
|
+
sleep_interval = 2
|
520
|
+
100.times do
|
521
|
+
sleep_interval = subject.next_sleep_interval(sleep_interval)
|
522
|
+
expect(sleep_interval).to be <= retry_max_interval
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
describe "stale connection check" do
|
528
|
+
let(:validate_after_inactivity) { 123 }
|
529
|
+
let(:options) { { "validate_after_inactivity" => validate_after_inactivity } }
|
530
|
+
let(:do_register) { false }
|
531
|
+
|
532
|
+
before :each do
|
533
|
+
allow(subject).to receive(:finish_register)
|
534
|
+
|
535
|
+
allow(::Manticore::Client).to receive(:new).with(any_args).and_call_original
|
536
|
+
end
|
537
|
+
|
538
|
+
after :each do
|
539
|
+
subject.close
|
540
|
+
end
|
541
|
+
|
542
|
+
it "should set the correct http client option for 'validate_after_inactivity'" do
|
543
|
+
subject.register
|
544
|
+
expect(::Manticore::Client).to have_received(:new) do |options|
|
545
|
+
expect(options[:check_connection_timeout]).to eq(validate_after_inactivity)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
describe "custom parameters" do
|
551
|
+
|
552
|
+
let(:manticore_urls) { subject.client.pool.urls }
|
553
|
+
let(:manticore_url) { manticore_urls.first }
|
554
|
+
|
555
|
+
let(:custom_parameters_hash) { { "id" => 1, "name" => "logstash" } }
|
556
|
+
let(:custom_parameters_query) { custom_parameters_hash.map {|k,v| "#{k}=#{v}" }.join("&") }
|
557
|
+
|
558
|
+
let(:stub_http_client_pool!) do
|
559
|
+
[:start_resurrectionist, :start_sniffer, :healthcheck!].each do |method|
|
560
|
+
allow_any_instance_of(LogStash::Outputs::OpenSearch::HttpClient::Pool).to receive(method)
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
context "using non-url hosts" do
|
565
|
+
|
566
|
+
let(:options) {
|
567
|
+
{
|
568
|
+
"index" => "my-index",
|
569
|
+
"hosts" => ["localhost:9202"],
|
570
|
+
"path" => "some-path",
|
571
|
+
"parameters" => custom_parameters_hash
|
572
|
+
}
|
573
|
+
}
|
574
|
+
|
575
|
+
it "creates a URI with the added parameters" do
|
576
|
+
expect(subject.parameters).to eql(custom_parameters_hash)
|
577
|
+
end
|
578
|
+
|
579
|
+
it "sets the query string on the HTTP client" do
|
580
|
+
expect(manticore_url.query).to eql(custom_parameters_query)
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
context "using url hosts" do
|
585
|
+
|
586
|
+
context "with embedded query parameters" do
|
587
|
+
let(:options) {
|
588
|
+
{ "hosts" => ["http://localhost:9202/path?#{custom_parameters_query}"] }
|
589
|
+
}
|
590
|
+
|
591
|
+
it "sets the query string on the HTTP client" do
|
592
|
+
expect(manticore_url.query).to eql(custom_parameters_query)
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
context "with explicit query parameters" do
|
597
|
+
let(:options) {
|
598
|
+
{
|
599
|
+
"hosts" => ["http://localhost:9202/path"],
|
600
|
+
"parameters" => custom_parameters_hash
|
601
|
+
}
|
602
|
+
}
|
603
|
+
|
604
|
+
it "sets the query string on the HTTP client" do
|
605
|
+
expect(manticore_url.query).to eql(custom_parameters_query)
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
context "with explicit query parameters and existing url parameters" do
|
610
|
+
let(:existing_query_string) { "existing=param" }
|
611
|
+
let(:options) {
|
612
|
+
{
|
613
|
+
"hosts" => ["http://localhost:9202/path?#{existing_query_string}"],
|
614
|
+
"parameters" => custom_parameters_hash
|
615
|
+
}
|
616
|
+
}
|
617
|
+
|
618
|
+
it "keeps the existing query string" do
|
619
|
+
expect(manticore_url.query).to include(existing_query_string)
|
620
|
+
end
|
621
|
+
|
622
|
+
it "includes the new query string" do
|
623
|
+
expect(manticore_url.query).to include(custom_parameters_query)
|
624
|
+
end
|
625
|
+
|
626
|
+
it "appends the new query string to the existing one" do
|
627
|
+
expect(manticore_url.query).to eql("#{existing_query_string}&#{custom_parameters_query}")
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
|
634
|
+
context 'handling elasticsearch document-level status meant for the DLQ' do
|
635
|
+
let(:options) { { "manage_template" => false } }
|
636
|
+
|
637
|
+
context 'when @dlq_writer is nil' do
|
638
|
+
before { subject.instance_variable_set '@dlq_writer', nil }
|
639
|
+
|
640
|
+
context 'resorting to previous behaviour of logging the error' do
|
641
|
+
context 'getting an invalid_index_name_exception' do
|
642
|
+
it 'should log at ERROR level' do
|
643
|
+
subject.instance_variable_set(:@logger, double("logger").as_null_object)
|
644
|
+
mock_response = { 'index' => { 'error' => { 'type' => 'invalid_index_name_exception' } } }
|
645
|
+
subject.handle_dlq_status("Could not index event to OpenSearch.",
|
646
|
+
[:action, :params, :event], :some_status, mock_response)
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
context 'when getting any other exception' do
|
651
|
+
it 'should log at WARN level' do
|
652
|
+
logger = double("logger").as_null_object
|
653
|
+
subject.instance_variable_set(:@logger, logger)
|
654
|
+
expect(logger).to receive(:warn).with(/Could not index/, hash_including(:status, :action, :response))
|
655
|
+
mock_response = { 'index' => { 'error' => { 'type' => 'illegal_argument_exception' } } }
|
656
|
+
subject.handle_dlq_status("Could not index event to OpenSearch.",
|
657
|
+
[:action, :params, :event], :some_status, mock_response)
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
context 'when the response does not include [error]' do
|
662
|
+
it 'should not fail, but just log a warning' do
|
663
|
+
logger = double("logger").as_null_object
|
664
|
+
subject.instance_variable_set(:@logger, logger)
|
665
|
+
expect(logger).to receive(:warn).with(/Could not index/, hash_including(:status, :action, :response))
|
666
|
+
mock_response = { 'index' => {} }
|
667
|
+
expect do
|
668
|
+
subject.handle_dlq_status("Could not index event to OpenSearch.",
|
669
|
+
[:action, :params, :event], :some_status, mock_response)
|
670
|
+
end.to_not raise_error
|
671
|
+
end
|
672
|
+
end
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
# DLQ writer always nil, no matter what I try here. So mocking it all the way
|
677
|
+
context 'when DLQ is enabled' do
|
678
|
+
let(:dlq_writer) { double('DLQ writer') }
|
679
|
+
before { subject.instance_variable_set('@dlq_writer', dlq_writer) }
|
680
|
+
|
681
|
+
# Note: This is not quite the desired behaviour.
|
682
|
+
# We should still log when sending to the DLQ.
|
683
|
+
# This shall be solved by another issue, however: logstash-output-elasticsearch#772
|
684
|
+
it 'should send the event to the DLQ instead, and not log' do
|
685
|
+
event = LogStash::Event.new("foo" => "bar")
|
686
|
+
expect(dlq_writer).to receive(:write).once.with(event, /Could not index/)
|
687
|
+
mock_response = { 'index' => { 'error' => { 'type' => 'illegal_argument_exception' } } }
|
688
|
+
action = LogStash::Outputs::OpenSearch::EventActionTuple.new(:action, :params, event)
|
689
|
+
subject.handle_dlq_status("Could not index event to OpenSearch.", action, 404, mock_response)
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
context 'with response status 400' do
|
694
|
+
|
695
|
+
let(:options) { super().merge 'document_id' => '%{foo}' }
|
696
|
+
|
697
|
+
let(:events) { [ LogStash::Event.new("foo" => "bar") ] }
|
698
|
+
|
699
|
+
let(:dlq_writer) { subject.instance_variable_get(:@dlq_writer) }
|
700
|
+
|
701
|
+
let(:bulk_response) do
|
702
|
+
{
|
703
|
+
"took"=>1, "ingest_took"=>11, "errors"=>true, "items"=>
|
704
|
+
[{
|
705
|
+
"index"=>{"_index"=>"bar", "_type"=>"_doc", "_id"=>'bar', "status"=>400,
|
706
|
+
"error"=>{"type" => "illegal_argument_exception", "reason" => "TEST" }
|
707
|
+
}
|
708
|
+
}]
|
709
|
+
}
|
710
|
+
end
|
711
|
+
|
712
|
+
before(:each) do
|
713
|
+
allow(subject.client).to receive(:bulk_send).and_return(bulk_response)
|
714
|
+
end
|
715
|
+
|
716
|
+
it "should write event to DLQ" do
|
717
|
+
expect(dlq_writer).to receive(:write).and_wrap_original do |method, *args|
|
718
|
+
expect( args.size ).to eql 2
|
719
|
+
|
720
|
+
event, reason = *args
|
721
|
+
expect( event ).to be_a LogStash::Event
|
722
|
+
expect( event ).to be events.first
|
723
|
+
expect( reason ).to start_with 'Could not index event to OpenSearch. status: 400, action: ["index"'
|
724
|
+
expect( reason ).to match /_id=>"bar".*"foo"=>"bar".*response:.*"reason"=>"TEST"/
|
725
|
+
|
726
|
+
method.call(*args) # won't hurt to call LogStash::Util::DummyDeadLetterQueueWriter
|
727
|
+
end.once
|
728
|
+
|
729
|
+
event_action_tuples = subject.map_events(events)
|
730
|
+
subject.send(:submit, event_action_tuples)
|
731
|
+
end
|
732
|
+
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
describe "custom headers" do
|
737
|
+
let(:manticore_options) { subject.client.pool.adapter.manticore.instance_variable_get(:@options) }
|
738
|
+
|
739
|
+
context "when set" do
|
740
|
+
let(:headers) { { "X-Thing" => "Test" } }
|
741
|
+
let(:options) { { "custom_headers" => headers } }
|
742
|
+
it "should use the custom headers in the adapter options" do
|
743
|
+
expect(manticore_options[:headers]).to eq(headers)
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
context "when not set" do
|
748
|
+
it "should have no headers" do
|
749
|
+
expect(manticore_options[:headers]).to be_empty
|
750
|
+
end
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
|
755
|
+
describe "post-register OpenSearch setup" do
|
756
|
+
let(:do_register) { false }
|
757
|
+
let(:version) { '7.10.0' }
|
758
|
+
let(:options) { { 'hosts' => '127.0.0.1:9999' } }
|
759
|
+
let(:logger) { subject.logger }
|
760
|
+
|
761
|
+
before do
|
762
|
+
allow(logger).to receive(:error) # expect tracking
|
763
|
+
|
764
|
+
allow(subject).to receive(:last_version).and_return version
|
765
|
+
# make successful_connection? return true:
|
766
|
+
allow(subject).to receive(:maximum_seen_major_version).and_return Integer(version.split('.').first)
|
767
|
+
allow(subject).to receive(:stop_after_successful_connection_thread)
|
768
|
+
end
|
769
|
+
|
770
|
+
it "logs inability to retrieve uuid" do
|
771
|
+
allow(subject).to receive(:install_template)
|
772
|
+
subject.register
|
773
|
+
subject.send :wait_for_successful_connection
|
774
|
+
|
775
|
+
expect(logger).to have_received(:error).with(/Unable to retrieve OpenSearch cluster uuid/i, anything)
|
776
|
+
end
|
777
|
+
|
778
|
+
it "logs template install failure" do
|
779
|
+
allow(subject).to receive(:discover_cluster_uuid)
|
780
|
+
subject.register
|
781
|
+
subject.send :wait_for_successful_connection
|
782
|
+
|
783
|
+
expect(logger).to have_received(:error).with(/Failed to install template/i, anything)
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
@private
|
788
|
+
|
789
|
+
def stub_manticore_client!(manticore_double = nil)
|
790
|
+
manticore_double ||= double("manticore #{self.inspect}")
|
791
|
+
response_double = double("manticore response").as_null_object
|
792
|
+
# Allow healtchecks
|
793
|
+
allow(manticore_double).to receive(:head).with(any_args).and_return(response_double)
|
794
|
+
allow(manticore_double).to receive(:get).with(any_args).and_return(response_double)
|
795
|
+
allow(manticore_double).to receive(:close)
|
796
|
+
|
797
|
+
allow(::Manticore::Client).to receive(:new).and_return(manticore_double)
|
798
|
+
end
|
799
|
+
|
800
|
+
end
|