logstash-mixin-scheduler 1.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 67978c6a1909fafc833c6b6ad0ea257274a7c6e9ae05dfd7c06e1a2b3d6820d0
4
+ data.tar.gz: cf0dada853778214e4bef783d356f921e0d153557912a6a00c787270decfaefe
5
+ SHA512:
6
+ metadata.gz: db8106c162a7268c40c3bfde3f7ee888d002238a9081f94eb294fa23af97eb687502a7243fd79b4e12c006574324bc1815b114966af43e42d73a837c7a4e3816
7
+ data.tar.gz: 0caadfd21d2b8e49f51b2b77cc3858423b0ec7fb21ba5aeab0839e2c17d308de34ecdcdea93e26e15cbc09b4bfccc435f5a05182e93a34eac5f69c3b019247a2
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 1.0.0
2
+ - Feat: a common scheduler interface [#1](https://github.com/logstash-plugins/logstash-mixin-scheduler/pull/1)
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2022 Elastic N.V. <http://www.elastic.co>
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Scheduler Mixin
2
+
3
+ [![Build Status](https://travis-ci.com/logstash-plugins/logstash-mixin-scheduler.svg?branch=main)](https://travis-ci.com/logstash-plugins/logstash-mixin-scheduler)
4
+
5
+
6
+ ## Usage
7
+
8
+ 1. Add this gem as a runtime dependency of your Logstash plugin's `gemspec`:
9
+
10
+ ~~~ ruby
11
+ Gem::Specification.new do |s|
12
+ # ...
13
+
14
+ s.add_runtime_dependency 'logstash-mixin-scheduler', '~> 1.0'
15
+ end
16
+ ~~~
17
+
18
+ 2. In your plugin code, require this library and include it into your plugin class
19
+ that already inherits `LogStash::Plugin`:
20
+
21
+ ~~~ ruby
22
+ require 'logstash/plugin_mixins/scheduler'
23
+
24
+ class LogStash::Inputs::Bar < Logstash::Inputs::Base
25
+
26
+ include LogStash::PluginMixins::Scheduler
27
+
28
+ # Schedule of when to periodically run statement, in Cron format
29
+ # for example: "* * * * *" (execute query every minute, on the minute)
30
+ config :schedule, :validate => :string
31
+
32
+ def run(queue)
33
+ if @schedule
34
+ scheduler.cron(@schedule) { serve_drinks(queue) }
35
+ scheduler.join
36
+ else
37
+ serve_drinks(queue)
38
+ end
39
+ end
40
+
41
+ # def server_drinks ...
42
+
43
+ end
44
+ ~~~
45
+
46
+ ## Development
47
+
48
+ This gem:
49
+ - *MUST* remain API-stable at 1.x
50
+ - *MUST NOT* introduce additional runtime dependencies
@@ -0,0 +1,243 @@
1
+ require 'rufus/scheduler'
2
+
3
+ require 'logstash/util/loggable'
4
+
5
+ module LogStash module PluginMixins module Scheduler module RufusImpl
6
+ # @private
7
+ class SchedulerAdapter
8
+ include SchedulerInterface
9
+
10
+ include LogStash::Util::Loggable
11
+
12
+ # @private
13
+ attr_reader :impl
14
+
15
+ def initialize(name, opts)
16
+ if name && !opts.key?(:thread_name)
17
+ opts[:thread_name] = name
18
+ end
19
+ opts[:max_work_threads] ||= 1
20
+ # amount the scheduler thread sleeps between checking whether jobs
21
+ # should trigger, default is 0.3 which is a bit too often ...
22
+ # in theory the cron expression '* * * * * *' supports running jobs
23
+ # every second but this is very rare, we could potentially go higher
24
+ opts[:frequency] ||= 1.0
25
+
26
+ logger = opts.delete(:logger) || self.logger
27
+ @impl = SchedulerImpl.new(opts, logger)
28
+ end
29
+
30
+ # @overload
31
+ def cron(schedule, opts = {}, &task); __schedule(:cron, schedule, opts, &task); end
32
+ # @overload
33
+ def every(period, opts = {}, &task); __schedule(:every, period, opts, &task); end
34
+ # @overload
35
+ def at(timestamp, opts = {}, &task); __schedule(:at, timestamp, opts, &task); end
36
+ # @overload
37
+ def in(delay, opts = {}, &task); __schedule(:in, delay, opts, &task); end
38
+ # @overload
39
+ def interval(interval, opts = {}, &task); __schedule(:interval, interval, opts, &task); end
40
+
41
+ # @overload
42
+ def release; @impl.shutdown end
43
+ # @overload
44
+ def release!; @impl.shutdown(:wait) end
45
+ # @overload
46
+ def running?; !@impl.down? end
47
+
48
+ # @overload
49
+ def join; @impl.join end
50
+
51
+ private
52
+
53
+ def __schedule(type, arg, opts, &task)
54
+ unless block_given?
55
+ raise ArgumentError, 'missing task - worker task to execute'
56
+ end
57
+ JobAdapter.new @impl.send(:"schedule_#{type}", arg, opts, &task)
58
+ end
59
+
60
+ end
61
+
62
+ # @private
63
+ class SchedulerImpl < ::Rufus::Scheduler
64
+
65
+ # Rufus::Scheduler >= 3.4 moved the Time impl into a gem EoTime = ::EtOrbi::EoTime`
66
+ # Rufus::Scheduler 3.1 - 3.3 using it's own Time impl `Rufus::Scheduler::ZoTime`
67
+ TimeImpl = defined?(::Rufus::Scheduler::EoTime) ? ::Rufus::Scheduler::EoTime :
68
+ (defined?(::Rufus::Scheduler::ZoTime) ? ::Rufus::Scheduler::ZoTime : ::Time)
69
+
70
+ def initialize(opts, logger)
71
+ super(opts)
72
+ @_logger = logger
73
+ end
74
+
75
+ # @overload
76
+ def join
77
+ fail NotRunningError.new('cannot join scheduler that is not running') unless @thread
78
+ fail ThreadError.new('scheduler thread cannot join itself') if @thread == Thread.current
79
+ @thread.join # makes scheduler.join behavior consistent across 3.x versions
80
+ end
81
+
82
+ # @overload
83
+ def shutdown(opt=nil)
84
+ if @thread # do not fail when scheduler thread failed to start
85
+ super(opt)
86
+ else
87
+ @started_at = nil # bare minimum to look like the scheduler is down
88
+ # when the scheduler thread fails `@started_at = ...` might still be set
89
+ end
90
+ end
91
+
92
+ # @overload
93
+ def timeout_jobs
94
+ # Rufus relies on `Thread.list` which is a blocking operation and with many schedulers
95
+ # (and threads) within LS will have a negative impact on performance as scheduler
96
+ # threads will end up waiting to obtain the `Thread.list` lock.
97
+ #
98
+ # However, this isn't necessary we can easily detect whether there are any jobs
99
+ # that might need to timeout: only when `@opts[:timeout]` is set causes worker thread(s)
100
+ # to have a `Thread.current[:rufus_scheduler_timeout]` that is not nil
101
+ return unless @opts[:timeout]
102
+ super
103
+ end
104
+
105
+ # @overload
106
+ def work_threads(query = :all)
107
+ if query == :__all_no_cache__ # special case from JobDecorator#start_work_thread
108
+ @_work_threads = nil # when a new worker thread is being added reset
109
+ return super(:all)
110
+ end
111
+
112
+ # Gets executed every time a job is triggered, we're going to cache the
113
+ # worker threads for this scheduler (to avoid `Thread.list`) - they only
114
+ # change when a new thread is being started from #start_work_thread ...
115
+ work_threads = @_work_threads
116
+ if work_threads.nil?
117
+ work_threads = threads.select { |t| t[:rufus_scheduler_work_thread] }
118
+ @_work_threads = work_threads
119
+ end
120
+
121
+ case query
122
+ when :active then work_threads.select { |t| t[:rufus_scheduler_job] }
123
+ when :vacant then work_threads.reject { |t| t[:rufus_scheduler_job] }
124
+ else work_threads
125
+ end
126
+ end
127
+
128
+ # @overload
129
+ def on_error(job, err)
130
+ details = { exception: err.class, message: err.message, backtrace: err.backtrace }
131
+ details[:cause] = err.cause if err.cause
132
+
133
+ details[:now] = debug_format_time(TimeImpl.now)
134
+ details[:last_time] = (debug_format_time(job.last_time) rescue nil)
135
+ details[:next_time] = (debug_format_time(job.next_time) rescue nil)
136
+ details[:job] = job
137
+
138
+ details[:opts] = @opts
139
+ details[:started_at] = started_at
140
+ details[:thread] = thread.inspect
141
+ details[:jobs_size] = @jobs.size
142
+ details[:work_threads_size] = work_threads.size
143
+ details[:work_queue_size] = work_queue.size
144
+
145
+ logger.error("Scheduler intercepted an error:", details)
146
+
147
+ rescue => e
148
+ logger.error("Scheduler failed in #on_error #{e.inspect}")
149
+ end
150
+
151
+ def debug_format_time(time)
152
+ # EtOrbi::EoTime used by (newer) Rufus::Scheduler has to_debug_s https://git.io/JyiPj
153
+ time.respond_to?(:to_debug_s) ? time.to_debug_s : time.strftime("%Y-%m-%dT%H:%M:%S.%L")
154
+ end
155
+ private :debug_format_time
156
+
157
+ # @private helper used by JobDecorator
158
+ def work_thread_name_prefix
159
+ ( @opts[:thread_name] || "#{@thread_key}_scheduler" ) + '_worker-'
160
+ end
161
+
162
+ def logger; @_logger end
163
+ private :logger
164
+
165
+ protected
166
+
167
+ # @overload
168
+ def start
169
+ ret = super() # @thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
170
+
171
+ # at least set thread.name for easier thread dump analysis
172
+ if @thread.is_a?(Thread) && @thread.respond_to?(:name=)
173
+ @thread.name = @thread[:name] if @thread[:name]
174
+ end
175
+
176
+ ret
177
+ end
178
+
179
+ # @overload
180
+ def do_schedule(job_type, t, callable, opts, return_job_instance, block)
181
+ job_or_id = super
182
+
183
+ job_or_id.extend JobDecorator if return_job_instance
184
+
185
+ job_or_id
186
+ end
187
+
188
+ module JobDecorator
189
+
190
+ def start_work_thread
191
+ prev_thread_count = @scheduler.work_threads.size
192
+
193
+ ret = super() # does not return Thread instance in 3.0
194
+
195
+ work_threads = @scheduler.work_threads(:__all_no_cache__)
196
+ while prev_thread_count == work_threads.size # very unlikely
197
+ Thread.pass
198
+ work_threads = @scheduler.work_threads(:__all_no_cache__)
199
+ end
200
+
201
+ work_thread_name_prefix = @scheduler.work_thread_name_prefix
202
+
203
+ work_threads.sort! do |t1, t2|
204
+ if t1[:name].nil?
205
+ t2[:name].nil? ? 0 : +1 # nils at the end
206
+ elsif t2[:name].nil?
207
+ t1[:name].nil? ? 0 : -1
208
+ else
209
+ t1[:name] <=> t2[:name]
210
+ end
211
+ end
212
+
213
+ work_threads.each_with_index do |thread, i|
214
+ unless thread[:name]
215
+ thread[:name] = "#{work_thread_name_prefix}#{sprintf('%02i', i)}"
216
+ thread.name = thread[:name] if thread.respond_to?(:name=)
217
+ end
218
+ end
219
+
220
+ ret
221
+ end
222
+
223
+ end
224
+ private_constant :JobDecorator
225
+
226
+ end
227
+
228
+ # @private
229
+ class JobAdapter
230
+ include JobInterface
231
+
232
+ # @private
233
+ attr_reader :impl
234
+
235
+ def initialize(job)
236
+ @impl = job
237
+ end
238
+
239
+ # @overload
240
+ def job_id; @impl.job_id end
241
+ end
242
+
243
+ end end end end
@@ -0,0 +1,139 @@
1
+ # encoding: utf-8
2
+
3
+ require 'logstash/namespace'
4
+ require 'logstash/plugin'
5
+ require 'logstash/util/thread_safe_attributes'
6
+
7
+ module LogStash
8
+ module PluginMixins
9
+ module Scheduler
10
+
11
+ extend LogStash::Util::ThreadSafeAttributes
12
+
13
+ ##
14
+ # @private
15
+ # @param base [Class]: a class that inherits `LogStash::Plugin`, typically one
16
+ # descending from one of the four plugin base classes
17
+ # (e.g., `LogStash::Inputs::Base`)
18
+ # @raise [ArgumentError]
19
+ # @return [void]
20
+ def self.included(base)
21
+ fail(ArgumentError, "`#{base}` must inherit LogStash::Plugin") unless base < LogStash::Plugin
22
+ instance_methods = base.instance_methods
23
+ base.send(:prepend, StopHook) if instance_methods.include?(:stop)
24
+ base.send(:prepend, CloseHook) if instance_methods.include?(:close)
25
+ end
26
+
27
+ # @private
28
+ module StopHook
29
+ def stop
30
+ release_scheduler! # wait till scheduler halts
31
+ super # plugin.stop
32
+ end
33
+ end
34
+ private_constant :StopHook
35
+
36
+ # @private
37
+ module CloseHook
38
+ def close
39
+ super # plugin.close
40
+ release_scheduler
41
+ end
42
+ end
43
+ private_constant :CloseHook
44
+
45
+ # def scheduler(); @_scheduler ||= new_scheduler({}) end
46
+ lazy_init_attr(:scheduler, variable: :@_scheduler) { start_scheduler({}) }
47
+
48
+ private
49
+
50
+ # Release jobs registered by the plugin from executing.
51
+ # This method executes from the plugin's #close method.
52
+ def release_scheduler
53
+ @_scheduler.release if @_scheduler
54
+ end
55
+
56
+ # Release jobs registered by the plugin from executing.
57
+ # This method executes from the plugin's #stop method.
58
+ # @note Might block until the scheduler operation completes!
59
+ def release_scheduler!
60
+ @_scheduler.release! if @_scheduler
61
+ end
62
+
63
+ # @param opts [Hash] scheduler options
64
+ # @return [SchedulerInterface] scheduler instance
65
+ def start_scheduler(opts, name: nil)
66
+ if name.nil?
67
+ unless self.class.name
68
+ raise ArgumentError, "can not generate a scheduler name for anonymous class: #{inspect}"
69
+ end
70
+ pipeline_id = (respond_to?(:execution_context) && execution_context&.pipeline_id) || 'main'
71
+ plugin_name = self.class.name.split('::').last # e.g. "jdbc"
72
+ name = "[#{pipeline_id}]|#{self.class.plugin_type}|#{plugin_name}|scheduler"
73
+ # thread naming convention: [psql1]|input|jdbc|scheduler
74
+ end
75
+ RufusImpl::SchedulerAdapter.new name, { logger: logger }.merge(opts)
76
+ end
77
+
78
+ # Scheduler interface usable by plugins.
79
+ module SchedulerInterface
80
+
81
+ # All scheduling methods return a `Scheduler::JobInterface`
82
+
83
+ # @return a job object which responds to a #job_id method
84
+ # @abstract
85
+ def cron(schedule, opts = {}, &task); fail NotImplementedError end
86
+ # @return a job object which responds to a #job_id method
87
+ # @abstract
88
+ def every(period, opts = {}, &task); fail NotImplementedError end
89
+ # @return a job object which responds to a #job_id method
90
+ # @abstract
91
+ def at(timestamp, opts = {}, &task); fail NotImplementedError end
92
+ # @return a job object which responds to a #job_id method
93
+ # @abstract
94
+ def in(delay, opts = {}, &task); fail NotImplementedError end
95
+ # @return a job object which responds to a #job_id method
96
+ # @abstract
97
+ def interval(interval, opts = {}, &task); fail NotImplementedError; end
98
+
99
+ # Blocks until _all_ jobs are joined, including jobs
100
+ # that are scheduled after this join has begun blocking.
101
+ # @abstract
102
+ def join; fail NotImplementedError end
103
+
104
+ # Release the scheduler:
105
+ # - prevents additional jobs from being registered,
106
+ # - and unschedules all future invocations of jobs previously registered
107
+ #
108
+ # This operation does not block until the scheduler stops executing jobs.
109
+ # @abstract
110
+ def release; fail NotImplementedError end
111
+
112
+ # Release the scheduler:
113
+ # - prevents additional jobs from being registered,
114
+ # - and unschedules all future invocations of jobs previously registered
115
+ #
116
+ # This operation attempts to WAIT until the scheduler operation completes (if supported).
117
+ # @abstract
118
+ def release!; release end
119
+
120
+ # Is this scheduler potentially executing our jobs.
121
+ #
122
+ # @abstract
123
+ # @see #release
124
+ def running?; fail NotImplementedError end
125
+
126
+ end
127
+
128
+ # Interface provided by a scheduled job.
129
+ module JobInterface
130
+
131
+ def job_id; fail NotImplementedError end
132
+
133
+ end
134
+
135
+ end
136
+ end
137
+ end
138
+
139
+ require 'logstash/plugin_mixins/scheduler/rufus_impl'
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/plugin_mixins/scheduler"
4
+
5
+ describe LogStash::PluginMixins::Scheduler::RufusImpl do
6
+
7
+ let(:name) { '[test]<jdbc_scheduler' }
8
+
9
+ let(:opts) do
10
+ { :max_work_threads => 2 }
11
+ end
12
+
13
+ subject(:scheduler) do
14
+ LogStash::PluginMixins::Scheduler::RufusImpl::SchedulerAdapter.new(name, opts)
15
+ end
16
+
17
+ after { scheduler.impl.shutdown }
18
+
19
+ it "sets scheduler thread name" do
20
+ expect( scheduler.impl.thread.name ).to include name
21
+ end
22
+
23
+ it "gets interrupted from join" do
24
+ scheduler.every('1s') { 42**1000 }
25
+ join_thread = Thread.start { scheduler.join }
26
+ sleep 1.1
27
+ expect(join_thread).to be_alive
28
+ expect(scheduler.impl.down?).to be false
29
+ scheduler.release
30
+ Thread.pass
31
+ expect(scheduler.impl.down?).to be true
32
+ sleep 0.1
33
+ try(10) { expect(join_thread).to_not be_alive }
34
+ end
35
+
36
+ it "gets interrupted from join (wait shutdown)" do
37
+ scheduler.cron('* * * * * *') { 42**1000 }
38
+ expect(scheduler.impl.down?).to be false
39
+ join_thread = Thread.start { scheduler.join }
40
+ sleep 1.1
41
+ expect(join_thread).to be_alive
42
+ scheduler.release!
43
+ expect(scheduler.impl.down?).to be true
44
+ sleep 0.1
45
+ try(10) { expect(join_thread).to_not be_alive }
46
+ end
47
+
48
+ context 'cron schedule' do
49
+
50
+ before do
51
+ scheduler.cron('* * * * * *') { sleep 1.25 } # every second
52
+ end
53
+
54
+ it "sets worker thread names" do
55
+ sleep 3.0
56
+ threads = scheduler.impl.work_threads
57
+ threads.sort! { |t1, t2| (t1.name || '') <=> (t2.name || '') }
58
+
59
+ expect( threads.size ).to eql 2
60
+ expect( threads.first.name ).to eql "#{name}_worker-00"
61
+ expect( threads.last.name ).to eql "#{name}_worker-01"
62
+ end
63
+
64
+ end
65
+
66
+ context 'every 1s' do
67
+
68
+ before do
69
+ scheduler.in('1s') { raise 'TEST' } # every second
70
+ end
71
+
72
+ it "logs errors handled" do
73
+ expect( scheduler.impl.send(:logger) ).to receive(:error).with /Scheduler intercepted an error/, hash_including(:message => 'TEST')
74
+ sleep 2.25
75
+ end
76
+
77
+ end
78
+
79
+ context 'work threads' do
80
+
81
+ let(:opts) { super().merge :max_work_threads => 3 }
82
+
83
+ let(:counter) { java.util.concurrent.atomic.AtomicLong.new(0) }
84
+
85
+ before do
86
+ scheduler.cron('* * * * * *') { counter.increment_and_get; sleep 3.25 } # every second
87
+ end
88
+
89
+ it "are working" do
90
+ sleep(0.05) while counter.get == 0
91
+ expect( scheduler.impl.work_threads.size ).to eql 1
92
+ sleep(0.05) while counter.get == 1
93
+ expect( scheduler.impl.work_threads.size ).to eql 2
94
+ sleep(0.05) while counter.get == 2
95
+ expect( scheduler.impl.work_threads.size ).to eql 3
96
+
97
+ sleep 1.25
98
+ expect( scheduler.impl.work_threads.size ).to eql 3
99
+ sleep 1.25
100
+ expect( scheduler.impl.work_threads.size ).to eql 3
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/plugin_mixins/scheduler"
4
+
5
+ module LogStash
6
+ module Inputs
7
+ class Foo < LogStash::Inputs::Base
8
+ include LogStash::PluginMixins::Scheduler
9
+ config_name 'foo'
10
+ end
11
+ end
12
+ end
13
+
14
+ module LogStash
15
+ module Filters
16
+ class Bar < LogStash::Inputs::Base
17
+ include LogStash::PluginMixins::Scheduler
18
+ config_name 'bar'
19
+ end
20
+ end
21
+ end
22
+
23
+ describe LogStash::PluginMixins::Scheduler do
24
+
25
+ subject(:mixin) { described_class }
26
+
27
+ context 'included into a class' do
28
+ context 'that does not inherit from `LogStash::Plugin`' do
29
+ let(:plugin_class) { Class.new }
30
+ it 'fails with an ArgumentError' do
31
+ expect do
32
+ plugin_class.send(:include, mixin)
33
+ end.to raise_error(ArgumentError, /LogStash::Plugin/)
34
+ end
35
+ end
36
+
37
+ [ LogStash::Inputs::Foo, LogStash::Filters::Bar ].each do |base_class|
38
+ context "#{base_class} plugin" do
39
+
40
+ let(:plugin_class) { base_class }
41
+
42
+ it 'works when mixin is includes and provides a scheduler method' do
43
+ plugin = plugin_class.new Hash.new
44
+ expect( plugin.scheduler ).to be_a LogStash::PluginMixins::Scheduler::SchedulerInterface
45
+ end
46
+
47
+ context 'hooks' do
48
+
49
+ let(:plugin) { plugin_class.new Hash.new }
50
+
51
+ it 'shuts-down the scheduler on close' do
52
+ scheduler = plugin.scheduler
53
+ expect( scheduler.running? ).to be true
54
+ plugin.do_close
55
+ expect( scheduler.running? ).to be false
56
+ end
57
+
58
+ it 'shuts-down the scheduler on stop' do
59
+ scheduler = plugin.scheduler
60
+ plugin.stop
61
+ expect( scheduler.running? ).to be false
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,3 @@
1
+ require 'rspec'
2
+
3
+ require "logstash-core"
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-mixin-scheduler
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: java
6
+ authors:
7
+ - Elastic
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-06-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.16'
19
+ name: logstash-core
20
+ prerelease: false
21
+ type: :runtime
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.16'
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.0.9
33
+ name: rufus-scheduler
34
+ prerelease: false
35
+ type: :runtime
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.9
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ name: logstash-devutils
48
+ prerelease: false
49
+ type: :development
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ name: logstash-codec-plain
62
+ prerelease: false
63
+ type: :development
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.9'
75
+ name: rspec
76
+ prerelease: false
77
+ type: :development
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.9'
83
+ description:
84
+ email: info@elastic.co
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - CHANGELOG.md
90
+ - LICENSE
91
+ - README.md
92
+ - lib/logstash/plugin_mixins/scheduler.rb
93
+ - lib/logstash/plugin_mixins/scheduler/rufus_impl.rb
94
+ - spec/logstash/plugin_mixins/scheduler/rufus_impl_spec.rb
95
+ - spec/logstash/plugin_mixins/scheduler_spec.rb
96
+ - spec/logstash/spec_helper.rb
97
+ homepage: https://github.com/logstash-plugins/logstash-mixin-scheduler
98
+ licenses:
99
+ - Apache-2.0
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubygems_version: 3.1.6
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Scheduler for Logstash plugins
120
+ test_files:
121
+ - spec/logstash/plugin_mixins/scheduler/rufus_impl_spec.rb
122
+ - spec/logstash/plugin_mixins/scheduler_spec.rb
123
+ - spec/logstash/spec_helper.rb