logstash-core 6.1.4-java → 6.2.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/logstash-core/logstash-core.rb +10 -31
  3. data/lib/logstash/agent.rb +3 -23
  4. data/lib/logstash/api/modules/logging.rb +11 -0
  5. data/lib/logstash/config/source/multi_local.rb +5 -3
  6. data/lib/logstash/environment.rb +10 -3
  7. data/lib/logstash/event.rb +0 -1
  8. data/lib/logstash/filter_delegator.rb +1 -2
  9. data/lib/logstash/inputs/base.rb +1 -1
  10. data/lib/logstash/instrument/periodic_poller/base.rb +4 -4
  11. data/lib/logstash/instrument/periodic_poller/jvm.rb +5 -3
  12. data/lib/logstash/java_filter_delegator.rb +1 -2
  13. data/lib/logstash/java_pipeline.rb +6 -2
  14. data/lib/logstash/modules/kibana_client.rb +1 -1
  15. data/lib/logstash/output_delegator.rb +2 -3
  16. data/lib/logstash/output_delegator_strategies/legacy.rb +4 -4
  17. data/lib/logstash/output_delegator_strategies/shared.rb +4 -4
  18. data/lib/logstash/output_delegator_strategies/single.rb +2 -2
  19. data/lib/logstash/pipeline.rb +16 -24
  20. data/lib/logstash/plugin.rb +1 -1
  21. data/lib/logstash/plugins/plugin_factory.rb +3 -4
  22. data/lib/logstash/runner.rb +5 -0
  23. data/lib/logstash/settings.rb +5 -0
  24. data/lib/logstash/timestamp.rb +2 -25
  25. data/lib/logstash/util/secretstore.rb +36 -0
  26. data/lib/logstash/util/settings_helper.rb +1 -0
  27. data/lib/logstash/util/substitution_variables.rb +18 -5
  28. data/lib/logstash/util/wrapped_acked_queue.rb +1 -1
  29. data/lib/logstash/util/wrapped_synchronous_queue.rb +3 -35
  30. data/locales/en.yml +4 -4
  31. data/logstash-core.gemspec +0 -7
  32. data/spec/conditionals_spec.rb +21 -24
  33. data/spec/logstash/filter_delegator_spec.rb +3 -4
  34. data/spec/logstash/java_filter_delegator_spec.rb +3 -4
  35. data/spec/logstash/java_pipeline_spec.rb +97 -2
  36. data/spec/logstash/legacy_ruby_timestamp_spec.rb +0 -1
  37. data/spec/logstash/output_delegator_spec.rb +5 -7
  38. data/spec/logstash/queue_factory_spec.rb +1 -1
  39. data/spec/logstash/settings_spec.rb +49 -22
  40. data/spec/logstash/timestamp_spec.rb +0 -1
  41. data/spec/logstash/util/secretstore_spec.rb +69 -0
  42. data/spec/support/mocks_classes.rb +21 -0
  43. data/versions-gem-copy.yml +2 -2
  44. metadata +6 -42
  45. data/gemspec_jars.rb +0 -12
  46. data/lib/logstash-core/logstash-core.jar +0 -0
  47. data/lib/logstash-core_jars.rb +0 -28
@@ -73,7 +73,7 @@ class LogStash::Plugin
73
73
  # close is called during shutdown, after the plugin worker
74
74
  # main task terminates
75
75
  def do_close
76
- @logger.debug("closing", :plugin => self.class.name)
76
+ @logger.debug("Closing", :plugin => self.class.name)
77
77
  close
78
78
  end
79
79
 
@@ -32,11 +32,10 @@ module LogStash
32
32
  class PluginFactory
33
33
  include org.logstash.config.ir.compiler.RubyIntegration::PluginFactory
34
34
 
35
- def initialize(lir, metric_factory, logger, exec_factory, filter_class)
35
+ def initialize(lir, metric_factory, exec_factory, filter_class)
36
36
  @lir = lir
37
37
  @plugins_by_id = {}
38
38
  @metric_factory = metric_factory
39
- @logger = logger
40
39
  @exec_factory = exec_factory
41
40
  @filter_class = filter_class
42
41
  end
@@ -83,9 +82,9 @@ module LogStash
83
82
  execution_context = @exec_factory.create(id, klass.config_name)
84
83
 
85
84
  if plugin_type == "output"
86
- OutputDelegator.new(@logger, klass, type_scoped_metric, execution_context, OutputDelegatorStrategyRegistry.instance, args)
85
+ OutputDelegator.new(klass, type_scoped_metric, execution_context, OutputDelegatorStrategyRegistry.instance, args)
87
86
  elsif plugin_type == "filter"
88
- @filter_class.new(@logger, klass, type_scoped_metric, execution_context, args)
87
+ @filter_class.new(klass, type_scoped_metric, execution_context, args)
89
88
  else # input or codec plugin
90
89
  plugin_instance = klass.new(args)
91
90
  scoped_metric = type_scoped_metric.namespace(id.to_sym)
@@ -85,6 +85,11 @@ class LogStash::Runner < Clamp::StrictCommand
85
85
  :attribute_name => "cloud.auth"
86
86
 
87
87
  # Pipeline settings
88
+ option ["--pipeline.id"], "ID",
89
+ I18n.t("logstash.runner.flag.pipeline-id"),
90
+ :attribute_name => "pipeline.id",
91
+ :default => LogStash::SETTINGS.get_default("pipeline.id")
92
+
88
93
  option ["-w", "--pipeline.workers"], "COUNT",
89
94
  I18n.t("logstash.runner.flag.pipeline-workers"),
90
95
  :attribute_name => "pipeline.workers",
@@ -9,6 +9,7 @@ module LogStash
9
9
  class Settings
10
10
 
11
11
  include LogStash::Util::SubstitutionVariables
12
+ include LogStash::Util::Loggable
12
13
 
13
14
  def initialize
14
15
  @settings = {}
@@ -27,6 +28,10 @@ module LogStash
27
28
  end
28
29
  end
29
30
 
31
+ def registered?(setting_name)
32
+ @settings.key?(setting_name)
33
+ end
34
+
30
35
  def get_setting(setting_name)
31
36
  setting = @settings[setting_name]
32
37
  raise ArgumentError.new("Setting \"#{setting_name}\" hasn't been registered") if setting.nil?
@@ -1,25 +1,2 @@
1
- # encoding: utf-8
2
-
3
- require "logstash/namespace"
4
-
5
- module LogStash
6
-
7
- class Timestamp
8
- include Comparable
9
-
10
- def eql?(other)
11
- self.== other
12
- end
13
-
14
- # TODO (colin) implement in Java
15
- def +(other)
16
- self.time + other
17
- end
18
-
19
- # TODO (colin) implement in Java
20
- def -(value)
21
- self.time - (value.is_a?(Timestamp) ? value.time : value)
22
- end
23
-
24
- end
25
- end
1
+ # The contents of this file have been ported to Java. It is included for for compatibility
2
+ # with plugins that directly require "logstash/timestamp".
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ # Ruby helper to work with the secret store
4
+ module ::LogStash::Util::SecretStore
5
+
6
+ java_import "org.logstash.secret.store.SecretStoreFactory"
7
+ java_import "org.logstash.secret.SecretIdentifier"
8
+ java_import "org.logstash.secret.store.SecureConfig"
9
+ java_import "org.logstash.secret.cli.SecretStoreCli"
10
+
11
+ # Return the configuration necessary to work with a secret store
12
+ def self.get_config
13
+ secure_config = SecureConfig.new
14
+ secure_config.add("keystore.file", LogStash::SETTINGS.get_setting("keystore.file").value.chars)
15
+ pass = ENV["LOGSTASH_KEYSTORE_PASS"]
16
+ secure_config.add("keystore.pass", pass.chars) unless pass.nil?
17
+ secure_config.add("keystore.classname", LogStash::SETTINGS.get_setting("keystore.classname").value.chars)
18
+ secure_config
19
+ end
20
+
21
+ # Check to see if the secret store exists, return true if exists, false otherwise
22
+ def self.exists?
23
+ SecretStoreFactory.exists(get_config)
24
+ end
25
+
26
+ # Returns a org.logstash.secret.store.SecretStore if it exists, nil otherwise
27
+ def self.get_if_exists
28
+ SecretStoreFactory.load(get_config) if exists?
29
+ end
30
+
31
+ # Returns a org.org.logstash.secret.SecretIdentifier for use with the secret store
32
+ def self.get_store_id(id)
33
+ SecretIdentifier.new(id)
34
+ end
35
+
36
+ end
@@ -32,6 +32,7 @@ module LogStash::Util::SettingsHelper
32
32
  settings_path = fetch_settings_path(args)
33
33
 
34
34
  LogStash::SETTINGS.set("path.settings", settings_path) if settings_path
35
+ LogStash::SETTINGS.set("keystore.file", ::File.join(settings_path, "logstash.keystore")) if settings_path
35
36
 
36
37
  begin
37
38
  LogStash::SETTINGS.from_yaml(LogStash::SETTINGS.get("path.settings"))
@@ -1,9 +1,15 @@
1
1
  # encoding: utf-8
2
+ require "logstash/logging"
3
+ require "logstash/util/loggable"
4
+ require "logstash/util/secretstore"
5
+
2
6
  module ::LogStash::Util::SubstitutionVariables
3
7
 
8
+ include LogStash::Util::Loggable
9
+
4
10
  SUBSTITUTION_PLACEHOLDER_REGEX = /\${(?<name>[a-zA-Z_.][a-zA-Z0-9_.]*)(:(?<default>[^}]*))?}/
5
11
 
6
- # Recursive method to replace environment variable references in parameters
12
+ # Recursive method to replace substitution variable references in parameters
7
13
  def deep_replace(value)
8
14
  if value.is_a?(Hash)
9
15
  value.each do |valueHashKey, valueHashValue|
@@ -22,7 +28,7 @@ module ::LogStash::Util::SubstitutionVariables
22
28
 
23
29
  # Replace all substitution variable references in the 'value' param and returns the substituted value, or the original value if a substitution can not be made
24
30
  # Process following patterns : ${VAR}, ${VAR:defaultValue}
25
- # If value matches the pattern, returns the following precedence : Environment entry value, default value as provided in the pattern
31
+ # If value matches the pattern, returns the following precedence : Secret store value, Environment entry value, default value as provided in the pattern
26
32
  # If the value does not match the pattern, the 'value' param returns as-is
27
33
  def replace_placeholders(value)
28
34
  return value unless value.is_a?(String)
@@ -34,12 +40,19 @@ module ::LogStash::Util::SubstitutionVariables
34
40
  # [1] http://ruby-doc.org/core-2.1.1/Regexp.html#method-c-last_match
35
41
  name = Regexp.last_match(:name)
36
42
  default = Regexp.last_match(:default)
43
+ logger.debug("Replacing `#{placeholder}` with actual value")
37
44
 
38
- replacement = ENV.fetch(name, default)
45
+ #check the secret store if it exists
46
+ secret_store = LogStash::Util::SecretStore.get_if_exists
47
+ replacement = secret_store.nil? ? nil : secret_store.retrieveSecret(LogStash::Util::SecretStore.get_store_id(name))
48
+ #check the environment
49
+ replacement = ENV.fetch(name, default) if replacement.nil?
39
50
  if replacement.nil?
40
- raise LogStash::ConfigurationError, "Cannot evaluate `#{placeholder}`. Environment variable `#{name}` is not set and there is no default value given."
51
+ raise LogStash::ConfigurationError, "Cannot evaluate `#{placeholder}`. Replacement variable `#{name}` is not defined in a Logstash secret store " +
52
+ "or as an Environment entry and there is no default value given."
41
53
  end
42
- replacement
54
+ replacement.to_s
43
55
  end
44
56
  end # def replace_placeholders
57
+
45
58
  end
@@ -94,7 +94,7 @@ module LogStash; module Util
94
94
  # from this queue. We also depend on this to be able to block consumers while we snapshot
95
95
  # in-flight buffers
96
96
 
97
- def initialize(queue, batch_size = 125, wait_for = 250)
97
+ def initialize(queue, batch_size = 125, wait_for = 50)
98
98
  @queue = queue
99
99
  @mutex = Mutex.new
100
100
  # Note that @inflight_batches as a central mechanism for tracking inflight
@@ -29,7 +29,7 @@ module LogStash; module Util
29
29
  # from this queue. We also depend on this to be able to block consumers while we snapshot
30
30
  # in-flight buffers
31
31
 
32
- def initialize(queue, batch_size = 125, wait_for = 250)
32
+ def initialize(queue, batch_size = 125, wait_for = 50)
33
33
  @queue = queue
34
34
  # Note that @inflight_batches as a central mechanism for tracking inflight
35
35
  # batches will fail if we have multiple read clients in the pipeline.
@@ -83,11 +83,11 @@ module LogStash; module Util
83
83
  # create a new empty batch
84
84
  # @return [ReadBatch] a new empty read batch
85
85
  def new_batch
86
- ReadBatch.new(@queue, 0, 0)
86
+ LogStash::MemoryReadBatch.new(java.util.LinkedHashSet.new(0))
87
87
  end
88
88
 
89
89
  def read_batch
90
- batch = ReadBatch.new(@queue, @batch_size, @wait_for)
90
+ batch = LogStash::MemoryReadBatch.new(LsQueueUtils.drain(@queue, @batch_size, @wait_for))
91
91
  start_metrics(batch)
92
92
  batch
93
93
  end
@@ -125,38 +125,6 @@ module LogStash; module Util
125
125
  end
126
126
  end
127
127
 
128
- class ReadBatch
129
- def initialize(queue, size, wait)
130
- # TODO: disabled for https://github.com/elastic/logstash/issues/6055 - will have to properly refactor
131
- # @cancelled = Hash.new
132
-
133
- @originals = LsQueueUtils.drain(queue, size, wait)
134
- end
135
-
136
- def merge(event)
137
- return if event.nil?
138
- @originals.add(event)
139
- end
140
-
141
- def to_a
142
- events = []
143
- @originals.each {|e| events << e unless e.cancelled?}
144
- events
145
- end
146
-
147
- def each(&blk)
148
- # below the checks for @cancelled.include?(e) have been replaced by e.cancelled?
149
- # TODO: for https://github.com/elastic/logstash/issues/6055 = will have to properly refactor
150
- @originals.each {|e| blk.call(e) unless e.cancelled?}
151
- end
152
-
153
- def filtered_size
154
- @originals.size
155
- end
156
-
157
- alias_method(:size, :filtered_size)
158
- end
159
-
160
128
  class WriteClient
161
129
  def initialize(queue)
162
130
  @queue = queue
@@ -58,9 +58,9 @@ en:
58
58
  sighup: >-
59
59
  SIGHUP received.
60
60
  sigint: >-
61
- SIGINT received. Shutting down the agent.
61
+ SIGINT received. Shutting down.
62
62
  sigterm: >-
63
- SIGTERM received. Shutting down the agent.
63
+ SIGTERM received. Shutting down.
64
64
  slow_shutdown: |-
65
65
  Received shutdown signal, but pipeline is still waiting for in-flight events
66
66
  to be processed. Sending another ^C will force quit Logstash, but this may cause
@@ -262,6 +262,8 @@ en:
262
262
  Check configuration for valid syntax and then exit.
263
263
  http_host: Web API binding host
264
264
  http_port: Web API http port
265
+ pipeline-id: |+
266
+ Sets the ID of the pipeline.
265
267
  pipeline-workers: |+
266
268
  Sets the number of pipeline workers to run.
267
269
  experimental-java-execution: |+
@@ -327,8 +329,6 @@ en:
327
329
  name: |+
328
330
  Specify the name of this logstash instance, if no value is given
329
331
  it will default to the current hostname.
330
- agent: |+
331
- Specify an alternate agent plugin name.
332
332
  config_debug: |+
333
333
  Print the compiled config ruby code out as a debug log (you must also have --log.level=debug enabled).
334
334
  WARNING: This will include any 'password' options passed to plugin configs as plaintext, and may result
@@ -70,13 +70,6 @@ Gem::Specification.new do |gem|
70
70
 
71
71
  gem.add_runtime_dependency "jrjackson", "~> #{ALL_VERSIONS.fetch('jrjackson')}" #(Apache 2.0 license)
72
72
 
73
- gem.add_runtime_dependency "jar-dependencies"
74
- # as of Feb 3rd 2016, the ruby-maven gem is resolved to version 3.3.3 and that version
75
- # has an rdoc problem that causes a bundler exception. 3.3.9 is the current latest version
76
- # which does not have this problem.
77
- gem.add_runtime_dependency "ruby-maven", "~> 3.3.9"
78
73
  gem.add_runtime_dependency "elasticsearch", "~> 5.0", ">= 5.0.4" # Ruby client for ES (Apache 2.0 license)
79
74
  gem.add_runtime_dependency "manticore", '>= 0.5.4', '< 1.0.0'
80
-
81
- eval(File.read(File.expand_path("../gemspec_jars.rb", __FILE__)))
82
75
  end
@@ -491,15 +491,14 @@ describe "conditionals in filter" do
491
491
  sample_one({"type" => "original"}) do
492
492
  expect(subject).to be_an(Array)
493
493
  expect(subject.length).to eq(2)
494
- subject.sort! {|a, b| a.get("type") <=> b.get("type")}
495
-
496
- expect(subject[1].get("type")).to eq("original")
497
- expect(subject[1].get("cond1")).to eq("true")
498
- expect(subject[1].get("cond2")).to eq(nil)
499
-
500
- expect(subject[0].get("type")).to eq("clone")
501
- # expect(subject[1].get("cond1")).to eq(nil)
502
- # expect(subject[1].get("cond2")).to eq("true")
494
+ original_event = subject[0]
495
+ expect(original_event.get("type")).to eq("original")
496
+ expect(original_event.get("cond1")).to eq("true")
497
+ expect(original_event.get("cond2")).to eq(nil)
498
+ cloned_event = subject[1]
499
+ expect(cloned_event.get("cond1")).to eq(nil)
500
+ expect(cloned_event.get("cond2")).to eq("true")
501
+ expect(cloned_event.get("type")).to eq("clone")
503
502
  end
504
503
  end
505
504
 
@@ -520,20 +519,18 @@ describe "conditionals in filter" do
520
519
  CONFIG
521
520
 
522
521
  sample_one({"type" => "original"}) do
523
- expect(subject.length).to eq(3)
524
- subject.sort! {|a, b| a.get("type") <=> b.get("type")}
525
-
526
- expect(subject[0].get("type")).to eq("clone1")
527
- expect(subject[0].get("cond1")).to eq("true")
528
- expect(subject[0].get("cond2")).to eq(nil)
529
-
530
- expect(subject[1].get("type")).to eq("clone2")
531
- expect(subject[1].get("cond1")).to eq(nil)
532
- expect(subject[1].get("cond2")).to eq("true")
533
-
534
- expect(subject[2].get("type")).to eq("original")
535
- expect(subject[2].get("cond1")).to eq(nil)
536
- expect(subject[2].get("cond2")).to eq(nil)
522
+ clone_event_1 = subject[0]
523
+ expect(clone_event_1.get("type")).to eq("clone1")
524
+ expect(clone_event_1.get("cond1")).to eq("true")
525
+ expect(clone_event_1.get("cond2")).to eq(nil)
526
+ clone_event_2 = subject[1]
527
+ expect(clone_event_2.get("type")).to eq("clone2")
528
+ expect(clone_event_2.get("cond1")).to eq(nil)
529
+ expect(clone_event_2.get("cond2")).to eq("true")
530
+ original_event = subject[2]
531
+ expect(original_event.get("type")).to eq("original")
532
+ expect(original_event.get("cond1")).to eq(nil)
533
+ expect(original_event.get("cond2")).to eq(nil)
537
534
  end
538
535
  end
539
536
 
@@ -586,7 +583,7 @@ describe "conditionals in filter" do
586
583
  expect(tags[6]).to eq("prev")
587
584
  expect(tags[7]).to eq("final")
588
585
  end
589
-
586
+
590
587
  sample_one("type" => "original") do
591
588
  tags = subject.get("tags")
592
589
  expect(tags[0]).to eq("prev")
@@ -14,8 +14,7 @@ describe LogStash::FilterDelegator do
14
14
  end
15
15
 
16
16
  include_context "execution_context"
17
-
18
- let(:logger) { double(:logger) }
17
+
19
18
  let(:filter_id) { "my-filter" }
20
19
  let(:config) do
21
20
  { "host" => "127.0.0.1", "id" => filter_id }
@@ -43,11 +42,11 @@ describe LogStash::FilterDelegator do
43
42
  end
44
43
  end
45
44
 
46
- subject { described_class.new(logger, plugin_klass, metric, execution_context, config) }
45
+ subject { described_class.new(plugin_klass, metric, execution_context, config) }
47
46
 
48
47
  it "create a plugin with the passed options" do
49
48
  expect(plugin_klass).to receive(:new).with(config).and_return(plugin_klass.new(config))
50
- described_class.new(logger, plugin_klass, metric, execution_context, config)
49
+ described_class.new(plugin_klass, metric, execution_context, config)
51
50
  end
52
51
 
53
52
  context "when the plugin support flush" do
@@ -14,8 +14,7 @@ describe LogStash::JavaFilterDelegator do
14
14
  end
15
15
 
16
16
  include_context "execution_context"
17
-
18
- let(:logger) { double(:logger) }
17
+
19
18
  let(:filter_id) { "my-filter" }
20
19
  let(:config) do
21
20
  { "host" => "127.0.0.1", "id" => filter_id }
@@ -43,11 +42,11 @@ describe LogStash::JavaFilterDelegator do
43
42
  end
44
43
  end
45
44
 
46
- subject { described_class.new(logger, plugin_klass, metric, execution_context, config) }
45
+ subject { described_class.new(plugin_klass, metric, execution_context, config) }
47
46
 
48
47
  it "create a plugin with the passed options" do
49
48
  expect(plugin_klass).to receive(:new).with(config).and_return(plugin_klass.new(config))
50
- described_class.new(logger, plugin_klass, metric, execution_context, config)
49
+ described_class.new(plugin_klass, metric, execution_context, config)
51
50
  end
52
51
 
53
52
  context "when the plugin support flush" do
@@ -355,7 +355,7 @@ describe LogStash::JavaPipeline do
355
355
  after do
356
356
  pipeline.shutdown
357
357
  end
358
-
358
+
359
359
  it "should call close of output without output-workers" do
360
360
  pipeline.run
361
361
 
@@ -380,7 +380,7 @@ describe LogStash::JavaPipeline do
380
380
  # cause the suite to fail :(
381
381
  pipeline.close
382
382
  end
383
-
383
+
384
384
  it "should use LIR provided IDs" do
385
385
  expect(pipeline.inputs.first.id).to eq(pipeline.lir.input_plugin_vertices.first.id)
386
386
  expect(pipeline.filters.first.id).to eq(pipeline.lir.filter_plugin_vertices.first.id)
@@ -642,6 +642,101 @@ describe LogStash::JavaPipeline do
642
642
  end
643
643
  end
644
644
 
645
+ context "Periodic Flush Wrapped in Nested Conditional" do
646
+ let(:config) do
647
+ <<-EOS
648
+ input {
649
+ dummy_input {}
650
+ }
651
+ filter {
652
+ if [type] == "foo" {
653
+ if [@bar] {
654
+ dummy_flushing_filter {}
655
+ }
656
+ } else {
657
+ drop {}
658
+ }
659
+ }
660
+ output {
661
+ dummy_output {}
662
+ }
663
+ EOS
664
+ end
665
+ let(:output) { ::LogStash::Outputs::DummyOutput.new }
666
+
667
+ before do
668
+ allow(::LogStash::Outputs::DummyOutput).to receive(:new).with(any_args).and_return(output)
669
+ allow(LogStash::Plugin).to receive(:lookup).with("input", "dummy_input").and_return(LogStash::Inputs::DummyBlockingInput)
670
+ allow(LogStash::Plugin).to receive(:lookup).with("filter", "dummy_flushing_filter").and_return(DummyFlushingFilterPeriodic)
671
+ allow(LogStash::Plugin).to receive(:lookup).with("output", "dummy_output").and_return(::LogStash::Outputs::DummyOutput)
672
+ allow(LogStash::Plugin).to receive(:lookup).with("filter", "drop").and_call_original
673
+ allow(LogStash::Plugin).to receive(:lookup).with("codec", "plain").and_return(LogStash::Codecs::Plain)
674
+ end
675
+
676
+ it "flush periodically" do
677
+ Thread.abort_on_exception = true
678
+ pipeline = mock_java_pipeline_from_string(config, pipeline_settings_obj)
679
+ t = Thread.new { pipeline.run }
680
+ Timeout.timeout(timeout) do
681
+ sleep(0.1) until pipeline.ready?
682
+ end
683
+ Stud.try(max_retry.times, [StandardError, RSpec::Expectations::ExpectationNotMetError]) do
684
+ wait(11).for do
685
+ # give us a bit of time to flush the events
686
+ output.events.size >= 2
687
+ end.to be_truthy
688
+ end
689
+
690
+ expect(output.events.any? {|e| e.get("message") == "dummy_flush"}).to eq(true)
691
+
692
+ pipeline.shutdown
693
+
694
+ t.join
695
+ end
696
+ end
697
+
698
+ context "with multiple outputs" do
699
+ let(:config) do
700
+ <<-EOS
701
+ input {
702
+ generator { count => 10 }
703
+ }
704
+ filter {
705
+ clone {
706
+ add_field => {
707
+ 'cloned' => 'cloned'
708
+ }
709
+ clones => ["clone1"]
710
+ }
711
+ }
712
+ output {
713
+ dummy_output {}
714
+ dummy_output {}
715
+ dummy_output {}
716
+ }
717
+ EOS
718
+ end
719
+ let(:output) { ::LogStash::Outputs::DummyOutput.new }
720
+
721
+ before do
722
+ allow(::LogStash::Outputs::DummyOutput).to receive(:new).with(any_args).and_return(output)
723
+ allow(LogStash::Plugin).to receive(:lookup).with("input", "generator").and_call_original
724
+ allow(LogStash::Plugin).to receive(:lookup).with("filter", "clone").and_call_original
725
+ 3.times {
726
+ allow(LogStash::Plugin).to receive(:lookup).with("output", "dummy_output").and_return(::LogStash::Outputs::DummyOutput)
727
+ allow(LogStash::Plugin).to receive(:lookup).with("codec", "plain").and_return(LogStash::Codecs::Plain)
728
+ }
729
+ end
730
+
731
+ it "correctly distributes events" do
732
+ pipeline = mock_java_pipeline_from_string(config, pipeline_settings_obj)
733
+ pipeline.run
734
+ pipeline.shutdown
735
+ expect(output.events.size).to eq(60)
736
+ expect(output.events.count {|e| e.get("cloned") == "cloned"}).to eq(30)
737
+ end
738
+ end
739
+
645
740
  context "#started_at" do
646
741
  # use a run limiting count to shutdown the pipeline automatically
647
742
  let(:config) do