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
         
     |